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:
- A Google Cloud Platform active account (you can also get a free tier account at https://cloud.google.com/free/)
- Google cloud platform SDK (you can follow instructions here)
- Kubernetes command line tool, kubectl (here you can find instructions on how to configure kubectl and glcoud for a basic project https://cloud.google.com/container-engine/docs/tutorials/hello-app)
- Docker installed on your machine
- Node.js and the node package manager (npm)
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:
- 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.
- appId : is the application ID managed by parse-server.
- masterKey : the masterKey is a typical API-Key that allows you to run api calls as an admin.
- 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.
- cloud : this parameter contains the path to the source code of cloud functions.
- mountPath : mount path for parse-server.
- 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.
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).
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.
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!