DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Securing Your Software Supply Chain with JFrog and Azure
Register Today

Trending

  • How To Use Geo-Partitioning to Comply With Data Regulations and Deliver Low Latency Globally
  • Manifold vs. Lombok: Enhancing Java With Property Support
  • Hiding Data in Cassandra
  • Docker Compose vs. Kubernetes: The Top 4 Main Differences

Trending

  • How To Use Geo-Partitioning to Comply With Data Regulations and Deliver Low Latency Globally
  • Manifold vs. Lombok: Enhancing Java With Property Support
  • Hiding Data in Cassandra
  • Docker Compose vs. Kubernetes: The Top 4 Main Differences
  1. DZone
  2. Coding
  3. Tools
  4. Deploying Containerized Applications on Google App Engine

Deploying Containerized Applications on Google App Engine

Take a look at this Node.js application and its deployment to Google App Engine for a better understanding of customizing container infrastructures.

Alan Hohn user avatar by
Alan Hohn
·
Updated Jun. 27, 18 · Tutorial
Like (5)
Save
Tweet
Share
7.77K Views

Join the DZone community and get the full member experience.

Join For Free

This article is featured in the new DZone Guide to Containers: Development and Management. Get your free copy for more insightful articles, industry statistics, and more!

Google App Engine

Platform-as-a-Service (PaaS) has become popular to build and deploy applications without having to worry about the underlying infrastructure that runs the application. When using PaaS, however, we need to make tradeoffs. Most importantly, when we give up control of the underlying infrastructure, we lose some ability to configure that infrastructure.

In this environment, a containerized PaaS such as Google App Engine (GAE) can provide a hybrid approach between letting the PaaS provider control the application runtime and having to build the runtime ourselves. Because applications in GAE run in Docker containers, we have the flexibility to customize the infrastructure while still being able to think in terms of deploying applications as opposed to deploying infrastructure. At the same time, there's a challenge in working with a PaaS like GAE because its application deployment behavior can be opaque. Sometimes, this means that you try a deployment before you know whether it will work.

To see how deployment to Google App Engine works and to understand some of the capabilities and challenges of customizing the infrastructure, let's look at a Node.js application deployed to GAE. This is very similar to an application that I built recently, but simplified for clarity. The example application provides a REST API that performs server-side rendering of Mermaid diagrams; this is challenging because it requires us to run a headless Chromium instance, which puts some unique demands on the application infrastructure.

To follow along, you can access the complete source code here. You'll need a GAE account and Google SDK installation. The installation instructions discuss running gcloud init, which will enable you to provide your account information and register your first application.

Note: While I'm describing customization in GAE, similar logic applies to customizing deployments in other common PaaS environments such as Heroku.

NODE.js GAE Application Structure

For a standard Node.js application, there is very little we need to deploy it to GAE. Most importantly, we need an app.yaml file at the top level. This can be very simple:

runtime: nodejs 
api_version:  '1.0' 
env: flexible 
threadsafe: true 
manual_scaling:
instances: 1


The two important lines in this file are env and runtime. The env line tells GAE to run this in the "flexible" environment, which means the runtime will use Docker. The runtimeline specifies the Node.js runtime.

Other than app.yaml, the repository is a standard Node.js server using Express. The only concession we must make to GAE is to accept a PORT environment variable telling us what port to listen on; this allows GAE to put a load balancer in front of our application. We handle this inside the server.js startup script for the application, defaulting to 3000 if PORTis not set:

...

const port =  process.env.PORT 3000;

...


The rest of server.js configures Express to route traffic to REST API endpoints. We start by configuring the body_ parser middleware:

...

app.use(bodyParser.text())

...

This tells Express to pass the request body through the text() parser. This parser reads the request body in as plain text. By default, however, it only handles HTTP requests where Content-Type is set to text/plain , so our client requests will need to match that expectation.

We next create the API endpoint /render:

...
app.post('/render',  function(req, res){
render(req.body, function(content)  {
res.send(content);
});
});
...


We provide a function that runs on a POST request to a specific URL path. By the time the function is called, the middleware has already read the request body, so we can access it as req.body.

Finally, we listen on the configured port:


...
app.listen(port, () =>  console.log(`Mermaid Server on port
${port}`));
...


The provided lambda function is called once the server is established.

Now that we have our Node.js application and our app.yaml file, we gcloud app deploy run to deploy the application to GAE. GAE will build a Docker container with our application files inside, run npm install , and then run npm start to run our application. It provides a load balancer that accepts traffic at the hostname identified when we created the application and performs HTTP/S termination for us so that we don't have to configure our own SSL certificate.

For our example application, since it exports a REST API, we can use httpie to send it a Mermaid text and get a diagram back.

File mermaid.txt :

graph TB
c1-->a2 subgraph one  a1-->a2
end
subgraph two  b1-->b2
end
subgraph three c1-->c2
end


Command:

cat  mermaid.txt |
http https://mermaid-server.appspot.com/render
'Content-Type:text/plain' >  render.png


This results in a PNG file with our rendered graphic:

Image title


Customizing the Container

As you can see, deploying a Node.js application to GAE is very simple. What makes this example a little more challenging is that it uses Mermaid, a JavaScript library that renders diagrams from a text description. Mermaid relies on having a browser available with SVG support. Fortunately, we can provide this on the server side in our Node.js application using Puppeteer. Puppeteer downloads and runs Chromium and allows us to interact with a Chromium instance directly from Node.js. This includes opening tabs, loading pages, running JavaScript, and taking screenshots of the rendered content.

When using PaaS, however, we need to make tradeoffs. Most importantly, when we give up control of the underlying infrastructure, we lose some ability to configure that infrastructure.

To get Chromium to work, we need some libraries that aren't present in the default Node.js container that GAE uses. We, therefore, need some way to get these libraries installed in our Docker container before Puppeteer will work as expected. One option would be to use runtime: custom in our app.yaml . This would tell GAE to look for a Dockerfile in the same directory as app.yaml. You can see an example of this in the "try-puppeteer" repository.

There is an alternative, however, that avoids the need for a Dockerfile. We can take advantage of Node.js preinstall to run commands when our container is set up. We include this in our package.json:

"scripts": {
"preinstall": "node preinstall.js"
},


This allows us to put any content we want into preinstall.js , and it will be executed prior to npm install .

In preinstall.js, we first check to see if we're running in production. If so, we use the child_process module to run the necessary apt-get commands to install Chromium dependencies.

const child_process =  require('child_process');
const  ne =  process.env.NODE_ENV;
if (ne  !== undefined  &&   ne ==  'production') { 
  console.log("Production  environment…"); 
  child_process.execSync('apt-get update',
stdio:'inherit'});
  child_process.execSync('apt-get -y install libxss1 
ibgconf-2-4  libatk-bridge2.0-0  libgtk-3-0  libx11-xcb1 
ibnss3  libasound2',  {stdio:'inherit'});
}  else  {
console.log("Production not detected,  nothing to do");
}


Because we're in a Docker container and already running as root, sudo is not necessary to run apt-get. The exact list of dependencies is, of course, based on what is needed for Chromium; I started with this post but tested the exact set of libraries needed so I could cut down the list somewhat.

The {stdio:'inherit'} configuration for execSync was essential in developing this; it causes Node.js to send the output of the apt-get commands to stdout, which means it shows up in the GAE logs for the application. This got me past a couple cases where package names had changed. The logs are available in the GAE console or using the gcloud app logs read command.

Conclusion

Platform-as-a-Service has made application deployment fast and easy by hiding the work of configuring the lower-level infrastructure. Sometimes, however, we need access to that infrastructure to configure it the way we want. For containerized PaaS environments such as Google App Engine, we can leverage the capabilities of the underlying Docker container to customize our application's infrastructure as needed without losing our simplified development and deployment model.

This article is featured in the new DZone Guide to Containers: Development and Management. Get your free copy for more insightful articles, industry statistics, and more!

application Google App Engine app Docker (software) Google (verb) Engine Infrastructure Node.js

Opinions expressed by DZone contributors are their own.

Trending

  • How To Use Geo-Partitioning to Comply With Data Regulations and Deliver Low Latency Globally
  • Manifold vs. Lombok: Enhancing Java With Property Support
  • Hiding Data in Cassandra
  • Docker Compose vs. Kubernetes: The Top 4 Main Differences

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: