s2i-dotnetcore icon indicating copy to clipboard operation
s2i-dotnetcore copied to clipboard

Investigate alternatives to including nodejs in our images

Open tmds opened this issue 4 years ago • 1 comments

Having node provides a simple way for users to build .NET web applications that use a JavaScript framework for their front-end, like Angular, React, ...

Some templates that come with .NET uses these frameworks, and they currently work out-of-the-box.

Including node also has downsides. It increases the size of the SDK image. We need to rebuild for upstream fixes, and we sometimes get reports from users for potential security issues.

This issue is to investigate alternatives for how such applications can be built on OpenShift if we would not include node in our images.

cc @aslicerh @omajid

tmds avatar Oct 27 '21 07:10 tmds

One option to work out may be to use a dockerStrategy.

I remember this used to be not allowed by default, but it seems it is now permitted. From https://access.redhat.com/documentation/en-us/openshift_container_platform/4.5/html/builds/securing-builds-by-strategy:

By default, all users that can create builds are granted permission to use the docker and Source-to-image (S2I) build strategies.

tmds avatar Nov 15 '21 13:11 tmds

Let's look into this once more for .NET 8.

tmds avatar Nov 21 '22 09:11 tmds

@omajid has updated the tests for the removal of the ASP.NET Core templates that use nodejs as their front-end in https://github.com/redhat-developer/dotnet-regular-tests/pull/277.

These templates were the main driver for us to include nodejs in our images. They are also what we're using to validate nodejs.

It makes sense to remove nodejs from our images for .NET 8.

If we remove it, we should describe how a user might update their existing .NET app. We can use an s2i build of one of the .NET 7 templates as a starting point.

tmds avatar Aug 25 '23 08:08 tmds

These templates were the main driver for us to include nodejs in our images.

I thought the reason we were doing this is that the users were likely to include SPA in their .NET code and would like to build both at the same time?

Microsoft's reasoning for removing the SPA templates was to encourage users to use the new Visual Studio js template system (https://github.com/dotnet/aspnetcore/issues/49388), not to suggest that users shouldn't use nodejs with .NET applications.

IMO, when we decide to remove (or not remove) nodejs, that decision should be made independent of whether there are .NET templates for it or not.

omajid avatar Aug 25 '23 13:08 omajid

I thought the reason we were doing this is that the users were likely to include SPA in their .NET code and would like to build both at the same time?

A pattern that was established by the templates included with the SDK.

Microsoft's reasoning for removing the SPA templates was to encourage users to use the new Visual Studio js template system (https://github.com/dotnet/aspnetcore/issues/49388), not to suggest that users shouldn't use nodejs with .NET applications.

What's not called out by this issue: when the SDK included the nodejs SPA front-ends, there were no .NET based alternatives. And now, while there aren't any more nodejs templates included, the SDK does come with templates that use Blazor for their front-end.

IMO, when we decide to remove (or not remove) nodejs, that decision should be made independent of whether there are .NET templates for it or not.

The templates were the motivation to include nodejs. They are also what we guarantee/validate to 'work' for each .NET release.

That the templates have been removed is a good reason to re-evaluate whether we still want to include nodejs, and for what reasons.

tmds avatar Aug 25 '23 14:08 tmds

If we remove it, we should describe how a user might update their existing .NET app. We can use an s2i build of one of the .NET 7 templates as a starting point.

I'm looking into this.

tmds avatar Oct 05 '23 06:10 tmds

This is how such a migration looks like.

We start with a .NET 7.0 web app that uses a NodeJS frontend like react or angular.

The application is deployed using s2i and refers to the dotnet:7.0 image from the imagestreams imported in the openshift namespace.

BuildConfig snippit:

...
  strategy:
    type: Source
    sourceStrategy:
      from:
        kind: ImageStreamTag
        namespace: openshift
        name: 'dotnet:7.0'
...

This dotnet:7.0 image imports registry.access.redhat.com/ubi8/dotnet-70 which includes NodeJS 18.

Now, we'll migrate to the .NET 8 s2i image, assuming it no longer includes NodeJS.

To minimize the changes to the application, we create a derived s2i image based on the .NET 8 base image where we install NodeJS 18 using a BuildConfig of the Docker type.

In the BuildConfig we'll use triggers to automatically re-build the image when either the .NET base image or the ubi NodeJS 18 image changes.

First, import the latest .NET and NodeJS definitions into the project namespace:

oc apply -f https://raw.githubusercontent.com/redhat-developer/s2i-dotnetcore/main/dotnet_imagestreams.json
oc apply -f https://raw.githubusercontent.com/sclorg/s2i-nodejs-container/master/imagestreams/nodejs-rhel.json

Then, import the following dotnet-8-node-18.yaml which defines the derived s2i image.

kind: ImageStream
apiVersion: image.openshift.io/v1
metadata:
  name: dotnet-8-node-18
---
kind: BuildConfig
apiVersion: build.openshift.io/v1
metadata:
  name: dotnet-8-node-18
spec:
  output:
    to:
      kind: ImageStreamTag
      name: 'dotnet-8-node-18:latest'
  strategy:
    type: Docker
    dockerStrategy:
      from:
        kind: ImageStreamTag
        name: dotnet:8.0
  source:
    type: Dockerfile
    dockerfile: |
      FROM dotnet

      USER 0
      ENV NODEJS_VERSION=18
      RUN microdnf -y module enable nodejs:$NODEJS_VERSION && \
          microdnf install -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 npm && \
          microdnf clean all -y && \
          rm -rf /var/cache/yum/*

      USER 1001
  triggers:
    - type: ConfigChange
    - type: ImageChange
    - type: ImageChange
      imageChange:
        from:
          kind: ImageStreamTag
          name: nodejs:18-ubi8
  runPolicy: Serial
oc apply -f dotnet-8-node18.yaml

We update the .NET project so it targets .NET 8:

 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <Nullable>enable</Nullable>
     <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
     <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>

And we update the BuildConfig to use the dotnet-8-node-18 image.

     sourceStrategy:
       from:
         kind: ImageStreamTag
-        namespace: openshift
-        name: 'dotnet:7.0'
+        name: 'dotnet-8-node-18:latest'

The s2i build is now using the derived image which includes .NET 8 and NodeJS 18.

For NodeJS to work well in the container, it may need some additional configuration, like configuring an NPM mirror of an HTTP proxy.

This can be achieved by adding an s2i assemble script in the source repository at .s2i/bin/assemble. If the s2i build uses a context dir, that path is under that directory.

The following shows an example script that performs initialization based on environment variables known by the .NET 7 s2i image.

#!/bin/bash

set -e

PATH=$HOME/node_modules/.bin:$PATH
if [ -n "$NPM_MIRROR" ]; then
  echo "---> Setting npm mirror"
  npm config set registry "$NPM_MIRROR"
fi
if [ -n "$HTTP_PROXY" ]; then
  echo "---> Setting npm http proxy"
  npm config set proxy "$HTTP_PROXY"
fi
if [ -n "$HTTPS_PROXY" ]; then
  echo "---> Setting npm https proxy"
  npm config set https-proxy "$HTTPS_PROXY"
fi
if [ -n "${DOTNET_NPM_TOOLS}" ]; then
  echo "---> Installing npm tools..."

  pushd "$HOME"
  npm install ${DOTNET_NPM_TOOLS}
  popd
fi

# call base .NET assemble script
$STI_SCRIPTS_PATH/assemble

This describes one option to migrate an app, and I think it covers the important aspects.

tmds avatar Oct 05 '23 09:10 tmds