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).
$ 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).
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.