How to troubleshoot Node.js images in OpenShift


Red Hat build of Node.js is a fully supported product within our portfolio and allows the deployment of JavaScript applications outside of a browser. Inside a container, it provides several features including an I/O model based on events and non-blocking operations for performant applications. Red Hat currently provides support for Node.js 20 links for Red Hat build of Node.js 20 LTS Component Details, Red Hat build of Node.js Supported Configurations, and Node.js Runtime Guide, which is an excellent guide. 

This article discusses how to troubleshoot build or deployment of Node.js in Red Hat OpenShift. However, I will not cover any advanced topics such as FIPS, OpenSSL, garbage collection tuning, or extensive Node.js manipulation.

I will present examples of Node.js build using a Dockerfile or a Source-to-Image (S2I) framework.

Node.js deployment via Dockerfile

Given the following hello.js file:

$ cat lib/hello.js 
console.log("Hello, Red Hat Developer Program World from Node " + process.version)

This is an example container file (Dockerfile) that can be used to build a container that runs the application when the container starts as follows:

FROM registry.access.redhat.com/ubi9/nodejs-20:1-24
COPY lib/hello.js /opt/app-root/src/hello.js
ENTRYPOINT ["node", "/opt/app-root/src/hello.js"]

When running the following, you will get the HelloWorld output:

$ podman run --rm -it  localhost/nodejs:app
Hello, Red Hat Developer Program World from Node v20.9.0

This is a less trivial example:

var http = require('http');
var port = 8000;
var laddr="0.0.0.0";
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello, Red Hat Developer Program World from ' +
        process.version + '!\n');
    console.log('Processed request for '+ req.url);
}).listen(port, laddr);
console.log('Server running at http://' + laddr + ':' + port + "https://developers.redhat.com/");

These examples do not take advantage of Source-to-Image, but rely directly on the injection of the application directly as entrypoint

Node.js Source-to-Image example

Here is a trivial application deployed by taking advantage of oc new-app command:

$ oc new-app nodejs:latest~https://github.com/sclorg/nodejs-ex.git
    Node.js 16 
    ---------- 
    Node.js 16 available as container is a base platform for building and running various Node.js 16 applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.
    Tags: builder, nodejs, nodejs16
    * A source build using source code from https://github.com/sclorg/nodejs-ex.git will be created
      * The resulting image will be pushed to image stream tag "nodejs-ex:latest"
      * Use 'oc start-build' to trigger a new build
--> Creating resources ...
    imagestream.image.openshift.io "nodejs-ex" created
    buildconfig.build.openshift.io "nodejs-ex" created
    deployment.apps "nodejs-ex" created
    service "nodejs-ex" created
--> Success
    Build scheduled, use 'oc logs -f buildconfig/nodejs-ex' to track its progress.

This oc client command uses the image Node.js tag latest, passing this GitHub path as an argument which will create a build in one container and then deploy the Node.js application in another. In this case, it is a create, read, update, delete (CRUD) application.

Going into a bit more in detail, for the Source-to-Image build process to happen, Red Hat provides a Node.js S2I builder image that assembles the application source plus the dependencies for a new Node.js container. What I mean by “builder image” is that the image will have the builder tag and the assemble and run scripts (Figure 1).

Node.js 20 builder image

Figure 1: Node.js builder image.
$ podman run --entrypoint=/bin/bash --rm -it  registry.redhat.io/rhel9/nodejs-20:1-48
bash-5.1$ cd /usr/libexec/s2i/
bash-5.1$ ls
assemble  run  save-artifacts  usage
...
...
bash-5.1$ cat assemble 
#!/bin/bash
# Prevent running assemble in builders different than official STI image.
# The official nodejs:8-onbuild already run npm install and use different
# application folder.
[ -d "/usr/src/app" ] && exit 0
set -e

Troubleshooting Node.js issues

There are numerous issues that can happen with Node.js deployment. They can be divided into two segments: building and deployment (runtime) issues.

Build issues

For this type of issue, collect the inspect file if the build is inside Red Hat OpenShift Container Platform (RHOCP) 4 or build resources such as Dockerfile if the build is done in Podman. Depending on the context, package.json could be useful as well.

Examples of issues that can occur during the OpenShift build include the Source-to-Image build process or image retrieval failures. Although the OpenShift 4 BuildConfig is quite common, there can be complex scenarios with Jenkins agents, the GitLab Runner Operator, and OpenShift sandboxed containers (OSC). That’s because depending on the build, the runtime, the CPU number, and availability might change, and this impacts builds. 

Examples of issues with Podman builds could also include the Node.js using Source-to-Image, and the injection of the applications in which the container would need to be built with an entry point that will start the application, such as the previous examples. 

There may also be issues with the Node Package Manager (NPM) installation and operations, such as installation libraries via NPM in this category. For instance, there may be issues with dependencies and profiles (which would be done via environment variables) usually done via NODE_ENV or declarative instruction RUN npm install --include=dev. For issues occurring during the NPM install process, package.json file can be useful to verify. 

Finally, in terms of deployment, Node.js applications often use some environment variables for their operations, and the building and/or deployment method might overwrite those variables.

bash-5.1$ echo $DESCRIPTION 
Node.js 20 available as container is a base platform for building and running various Node.js 20 applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.
bash-5.1$ echo $SUMMARY   
Platform for building and running Node.js 20 applications
bash-5.1$ echo $NPM_RUN
start
bash-5.1$ echo $NPM_CONFIG_PREFIX 
/opt/app-root/src/.npm-global

This is slightly specific to the Next.js framework, but also related to environment variables; and therefore might be mistakenly overwritten on the build as the NEXT_PUBLIC and  NEXT_PUBLIC_VAR environment variables.

You can find more example issues here and here.

Deployment issues

For this deployment issues, collecting the inspect file and/or specific resources such as garbage collection data can be useful. For example, in the case of CPU discrepancies, the output of /proc/stat can be useful, as this will bring CPU utilization from the container.

For memory issues, collecting the garbage collection logs can be useful. To collect garbage collection logs, start the application with –trace-gc as follows:

 ENTRYPOINT ["node", "--trace-gc", "/opt/app-root/src/hello.js"] <-- to allow trace-gc

When investigating memory consumption, another option in terms of memory issues is the capture of heap snapshots. There are several options for taking a heap snapshot, for instance, via external signal and command line flag. For heap snapshot refer to this guide. Evidently for containers, triggering via signal such as kill -USR1 $PID is very useful.

After collecting the heap snapshot, to analyze heap snapshots, open the Chromium base browser Dev tools and navigate to the Memory tab. This browser can also be useful for comparing different snapshots. There is a feature on the browser to do so (Figure 2).

Heap snapshot

Figure 2: A snapshot of the heap.

To diagnose a real leak, one must increase the memory a few times and wait for the increase. I would suggest increasing the memory to 50% or even 100%, rather than 10% as suggested on the upstream documentation until you start seeing the OOM again:

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory [...]

In terms of memory usage, it can come in handy to diagnose issues such as increasing or reducing --max-old-space-size in terms of memory consumption or leaks. 

In the case of an upgraded OpenShift cluster that has a different performance, by changing the number of requests, it is interesting to have networking data or performance data. 

Finally, having an agent for collecting data can be very useful. In this example of requests, some agents might be able to see what the bulk of the requests are during a certain period.

In terms of OpenShift Container Platform 4 build and deployment, collecting the inspect can be a good alternative, given that it will provide logs and YAMLs for both build and deployment resources, such as pod YAML/deployment YAML. inspect is useful for most middleware issues in general. 

Read the article, Using OpenShift 4’s inspect for middleware troubleshooting and note the namespace’s inspect won’t bring workload data, but rather the collection of OpenShift Container Platform 4 deployment YAMLs, which need the specific deployment and pod names.

For builds outside OpenShift Container Platform via Podman/Docker, the Dockerfile and the logs will be needed as mentioned.

Finally, the Runtime Guide brings details on debugging the Node.js application using the native debugger. 

Note that Node.js inspect command (node inspect EXAMPLE.js) is different from OpenShift inspect bundle ($ oc adm inspect ns/NAMESPACE). Node.js inspect concerns a command-line debugging utility that is started with inspect command, whereas OpenShift Inspect is the bundle that contains runtime resources. 

Additional resources

In summation, Node.js is based on Google’s V8 JavaScript and allows server-side JavaScript applications. Red Hat’s build of Node.js containerized image enables those features in a container, which is easy to use and scale, and also allows container deployment features in OpenShift 4, such as HorizontalPodAutoscaler and container settings via cgroups.

Although not extensive, this article sheds light on the matter of troubleshooting and clarifying relevant data to be collected and how to collect it. In the future, I plan to dive into garbage collection logs and tuning in Node.js.

To learn more about Node.js see the Node.js Runtime Guide and upstream pages. Also, the GitHub page is a good reference, covering topics from building good containers to debugging, mostly agnostic from Red Hat-specific implementation. In terms of tracing/debugging, there is the Observability for Node.js applications in OpenShift series.

For any other specific inquiries, please open a case with Red Hat support. Our global team of experts will be glad to help you with any issues.

Special thanks to Alexander Barbosa for the excellent review and Michael Dawson for his great input and collaboration for Node.js issues in OpenShift throughout the years.



Source link

Related Posts

About The Author

Add Comment