Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Deploying Containerized Applications on Google App Engine

DZone's Guide to

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.

· Cloud Zone ·
Free Resource

Site24x7 - Full stack It Infrastructure Monitoring from the cloud. Sign up for free trial.

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!

Site24x7 - Full stack It Infrastructure Monitoring from the cloud. Sign up for free trial.

Topics:
cloud ,google app engine ,containers ,containerization ,node.js ,customizatio ,cloud infrastructure

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}