Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExecJS can't find a runtime #447

Closed
maxboone opened this issue Nov 1, 2021 · 23 comments
Closed

ExecJS can't find a runtime #447

maxboone opened this issue Nov 1, 2021 · 23 comments

Comments

@maxboone
Copy link

maxboone commented Nov 1, 2021

Possibly not related to this specific image, but ExecJS can't find a runtime when we try to run this image as non-root:

User is configured:

~ $ whoami
app

Binary is set (forked the image, and changed the perms on this binary, but not necessary for execution per se):

~ $ ls -all /usr/bin/node 
-rwxr-xr-x    1 app      app       36962824 Oct 13 10:24 /usr/bin/node

It is also linked in PATH:

~ $ which node
/usr/bin/node

And works fine:

~ $ node -e 'console.log(42)'
42

However:

~ $ puma -C config/puma.rb 
Puma starting in single mode...
* Puma version: 5.5.2 (ruby 2.6.8-p205) ("Zawgyi")
*  Min threads: 5
*  Max threads: 5
*  Environment: production
*          PID: 26
! Unable to load application: ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.
/usr/local/bundle/gems/execjs-2.8.1/lib/execjs/runtimes.rb:58:in `autodetect': Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)
	from /usr/local/bundle/gems/execjs-2.8.1/lib/execjs.rb:5:in `<module:ExecJS>'
	from /usr/local/bundle/gems/execjs-2.8.1/lib/execjs.rb:4:in `<main>'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
	from /usr/local/bundle/gems/bundler-2.2.22/lib/bundler/runtime.rb:66:in `block (2 levels) in require'
	from /usr/local/bundle/gems/bundler-2.2.22/lib/bundler/runtime.rb:61:in `each'
	from /usr/local/bundle/gems/bundler-2.2.22/lib/bundler/runtime.rb:61:in `block in require'
	from /usr/local/bundle/gems/bundler-2.2.22/lib/bundler/runtime.rb:50:in `each'
	from /usr/local/bundle/gems/bundler-2.2.22/lib/bundler/runtime.rb:50:in `require'
	from /usr/local/bundle/gems/bundler-2.2.22/lib/bundler.rb:174:in `require'
	from /app/config/application.rb:16:in `<top (required)>'
	from /app/config/environment.rb:2:in `require_relative'
	from /app/config/environment.rb:2:in `<top (required)>'
	from config.ru:3:in `require_relative'
	from config.ru:3:in `block in <main>'
	from /usr/local/bundle/gems/rack-2.2.3/lib/rack/builder.rb:116:in `eval'
	from /usr/local/bundle/gems/rack-2.2.3/lib/rack/builder.rb:116:in `new_from_string'
	from /usr/local/bundle/gems/rack-2.2.3/lib/rack/builder.rb:105:in `load_file'
	from /usr/local/bundle/gems/rack-2.2.3/lib/rack/builder.rb:66:in `parse_file'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/configuration.rb:345:in `load_rackup'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/configuration.rb:267:in `app'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/runner.rb:149:in `load_and_bind'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/single.rb:44:in `run'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/launcher.rb:181:in `run'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/cli.rb:80:in `run'
	from /usr/local/bundle/gems/puma-5.5.2/bin/puma:10:in `<top (required)>'
	from /usr/local/bundle/bin/puma:23:in `load'
	from /usr/local/bundle/bin/puma:23:in `<main>'
@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

This also happens in the repository's version of Ruby, but tried a downgrade bc of issues at the execjs front:

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

It does seem to be related to the issue that's attached to rails/execjs:

~ $ cat test
puts File.stat('/usr/bin/node').owned?
puts File.executable?('/usr/bin/node')
puts File.file?('/usr/bin/node')
~ $ ruby test 
true
false
true

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Doesn't seem to be a kernel / alpine issue:

~ $ [[ -x '/usr/bin/node' ]]
~ $ echo $?
0

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Considering the OS seems to be doing fine on this end, possibly going to monkeypatch this for the time being

@klausmeyer
Copy link
Owner

Thanks for looking into this topic and providing all the details 🙇🏻‍♂️

This confirms that the issue is somehow related to a combination of the used base-image and other influences from the docker-host itself. Wish I would be able to reproduce it myself somehow.

But what I would like to check anyways is if I can get rid of the need of a JS runtime in general.
In theory it's only needed to precompile the assets during the image-build. In the run it might be even obsolete.

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Just manually patched the gem so that it returns executable? => true when /usr/bin/node is being checked (as I know that one is on the image). However, upon running it now 404s on all the assets (incl. JS)

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Begs the question if possibly something went wrong during build-time, that would explain why Ruby tries to run node?

@klausmeyer
Copy link
Owner

klausmeyer commented Nov 1, 2021

Just manually patched the gem so that it returns executable? => true when /usr/bin/node is being checked (as I know that one is on the image). However, upon running it now 404s on all the assets (incl. JS)

Hm ... just to understand better - how exactly did you patch the gem?
Manually inside the container or did you build a new image? The 404 part is strange - that's why I'm asking.

Begs the question if possibly something went wrong during build-time, that would explain why Ruby tries to run node?

Currently looking into it :-)

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Hm ... just to understand better - how exactly did you patch the gem? Manually inside the container or did you build a new image? The 404 part is strange - that's why I'm asking.

Patched it in a very ugly way 😛

--- /usr/local/bundle/gems/execjs-2.8.1/lib/execjs/external_runtime.rb
+++ /usr/local/bundle/gems/execjs-2.8.1/lib/execjs/external_runtime.rb
@@ -129,12 +129,18 @@
         end
 
         commands.find { |cmd|
+          if cmd == '/usr/bin/node'
+            return true
+          end
           if File.executable? cmd
             cmd
           else
             path = ENV['PATH'].split(File::PATH_SEPARATOR).find { |p|
               full_path = File.join(p, cmd)
               File.executable?(full_path) && File.file?(full_path)
+              if full_path == '/usr/bin/node'
+                return true
+              end
             }
             path && File.expand_path(cmd, path)
           end

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Not acquainted with ruby at all, so there's probably a way better solution to do this actually 😅

In a small script you can easily patch it using

class File
  def self.executable?(target)
    # ... code ...
  end
end

But considering a gem is running it, not sure how that hooks into the application

@klausmeyer
Copy link
Owner

Doesn't need to be perfect code to just see if it fixes the problem 😁
What I was actually trying to ask was how did you then apply this change?
Did you somehow manually start the server then? The 404 could be related to a missing ENV variable.

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Ah, cool!

Yeah, I edited the file (/usr/local/bundle/gems/execjs-2.8.1/lib/execjs/external_runtime.rb) to add those lines in, so execjs uses /usr/bin/node as ExternalRuntime and then just puma -C config/puma.rb

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

~ $ env
RUBY_MAJOR=2.6
SSL_PORT=_snip_
RAILS_LOG_TO_STDOUT=true
KUBERNETES_SERVICE_PORT=_snip_
REGISTRY_BROWSER_PORT_8080_TCP_PORT=_snip_
KUBERNETES_PORT=_snip_
REGISTRY_BROWSER_PORT_8080_TCP_PROTO=tcp
HOSTNAME=_snip_
SOURCE_COMMIT=
TOKEN_AUTH_PASSWORD=_snip_
REGISTRY_BROWSER_PORT=_snip_
REGISTRY_BROWSER_SERVICE_PORT=8080
SHLVL=2
PORT=8080
ENABLE_COLLAPSE_NAMESPACES=true
HOME=/app
TOKEN_AUTH_USER=_snip_
REGISTRY_BROWSER_PORT_8080_TCP=_snip_
BUNDLE_APP_CONFIG=/usr/local/bundle
RUBY_VERSION=2.6.8
TERM=xterm
KUBERNETES_PORT_443_TCP_ADDR=_snip_
DOCKER_REGISTRY_URL=_snip_
PATH=/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
LANG=C.UTF-8
GEM_HOME=/usr/local/bundle
RAILS_ENV=production
REGISTRY_BROWSER_SERVICE_PORT_HTTP=8080
SECRET_KEY_BASE=_snip_
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=_snip_
RUBY_DOWNLOAD_SHA256=_snip_
RAILS_SERVE_STATIC_FILES=true
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/app
BUNDLE_SILENCE_ROOT_WARNING=1
REGISTRY_BROWSER_PORT_8080_TCP_ADDR=_snip_
REGISTRY_BROWSER_SERVICE_HOST=_snip_

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Let me bump the container image to ruby version 3 again, possibly it's not working properly because of the downgrade (unfortunately can't make the container public)

@klausmeyer
Copy link
Owner

Ok I was talking about RAILS_SERVE_STATIC_FILES - so it's maybe then not just that.

Anyways I've figured out in the meanwhile that I can't really get rid of the need of a JS runtime as one of the gems I'm using will always require it as one of it's downstream dependencies - even in the run 😞

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Ah, interestingly - the files that it 404s on are in the public directory so that doesn't seem related per se.

@klausmeyer
Copy link
Owner

klausmeyer commented Nov 1, 2021

Usually there should be files in public/assets/....
They're generated by the rails assets:precompile in the Dockerfile.

But coming back to the actual issue with the File.executable?. I've now read a bit in the issues you linked above and I remember that I came across similar issues before. It has something to do with the underlaying syscalls and their mapping:

Alpine 3.14 seems to have known problems when running on a host with older libseccomp. Do you by any chance have access on the docker host and can check what version is installed there?

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

Yeah, let me check - I did try to rule that out by doing an -x check through busybox, but it seems that you're right indeed.

$ runc -v
runc version 1.0.0-rc10
commit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
spec: 1.0.1-dev
$ ctr -v
ctr github.com/containerd/containerd v1.3.3

@klausmeyer
Copy link
Owner

klausmeyer commented Nov 1, 2021

I'm running the app myself on a system with the following specs:

~ ❯ uname -a
Linux km-pi3b 5.10.63-v7+ #1459 SMP Wed Oct 6 16:41:10 BST 2021 armv7l GNU/Linux

~ ❯ lsb_release -a
No LSB modules are available.
Distributor ID:	Raspbian
Description:	Raspbian GNU/Linux 10 (buster)
Release:	10
Codename:	buster

~ ❯ docker --version
Docker version 20.10.10, build b485636

~ ❯ runc -v                                                                                                                                                                  ⏎
runc version 1.0.2
commit: v1.0.2-0-g52b36a2
spec: 1.0.2-dev
go: go1.16.8
libseccomp: 2.5.1

~ ❯ ctr -v
ctr containerd.io 1.4.11

Note: I also had to update the libseccomp here manually - otherwise I had problems with (other) syscalls.
Maybe worth a try on your end as well if possible.

@maxboone
Copy link
Author

maxboone commented Nov 1, 2021

TL;DR for other users running into this issue - update your containerd!


If you can't, then override the check from execjs, personally I used this patch for it:

--- external_runtime.rb
+++ external_runtime.rb
@@ -129,12 +129,18 @@
         end
 
         commands.find { |cmd|
+          if cmd == '/usr/bin/node'
+            return true
+          end
           if File.executable? cmd
             cmd
           else
             path = ENV['PATH'].split(File::PATH_SEPARATOR).find { |p|
               full_path = File.join(p, cmd)
               File.executable?(full_path) && File.file?(full_path)
+              if full_path == '/usr/bin/node'
+                return true
+              end
             }
             path && File.expand_path(cmd, path)
           end

And added this to the Dockerfile:

RUN patch /usr/local/bundle/gems/execjs-2.8.1/lib/execjs/external_runtime.rb < patches/force-node-execjs.patch

@maxboone maxboone closed this as completed Nov 1, 2021
@klausmeyer
Copy link
Owner

Some additional notes:

I was able to reproduce it now by forcing the installation of older versions on a Debian 10 VM:

vagrant@buster:~$ sudo apt-get install docker-ce=5:19.03.12~3-0~debian-buster containerd.io=1.2.2-3
vagrant@buster:~$ uname -a
Linux buster 4.19.0-18-amd64 #1 SMP Debian 4.19.208-1 (2021-09-29) x86_64 GNU/Linux
vagrant@buster:~$ lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 10 (buster)
Release:	10
Codename:	buster
vagrant@buster:~$ docker version --format '{{.Server.Version}}'
19.03.12
vagrant@buster:~$ ctr -v
ctr github.com/containerd/containerd 1.2.2
vagrant@buster:~$ docker run -it --rm klausmeyer/docker-registry-browser:latest
Puma starting in single mode...
* Puma version: 5.5.2 (ruby 3.0.2-p107) ("Zawgyi")
*  Min threads: 5
*  Max threads: 5
*  Environment: production
*          PID: 1
! Unable to load application: ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.
/usr/local/bundle/gems/execjs-2.7.0/lib/execjs/runtimes.rb:58:in `autodetect': Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)
	from /usr/local/bundle/gems/execjs-2.7.0/lib/execjs.rb:5:in `<module:ExecJS>'
	from /usr/local/bundle/gems/execjs-2.7.0/lib/execjs.rb:4:in `<main>'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
	from /usr/local/bundle/gems/activesupport-6.1.4.1/lib/active_support/dependencies.rb:332:in `block in require'
	from /usr/local/bundle/gems/activesupport-6.1.4.1/lib/active_support/dependencies.rb:299:in `load_dependency'
	from /usr/local/bundle/gems/activesupport-6.1.4.1/lib/active_support/dependencies.rb:332:in `require'
	from /usr/local/bundle/gems/uglifier-4.2.0/lib/uglifier.rb:5:in `<main>'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
	from /usr/local/bundle/gems/bootsnap-1.9.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
	from /usr/local/bundle/gems/bundler-2.2.15/lib/bundler/runtime.rb:66:in `block (2 levels) in require'
	from /usr/local/bundle/gems/bundler-2.2.15/lib/bundler/runtime.rb:61:in `each'
	from /usr/local/bundle/gems/bundler-2.2.15/lib/bundler/runtime.rb:61:in `block in require'
	from /usr/local/bundle/gems/bundler-2.2.15/lib/bundler/runtime.rb:50:in `each'
	from /usr/local/bundle/gems/bundler-2.2.15/lib/bundler/runtime.rb:50:in `require'
	from /usr/local/bundle/gems/bundler-2.2.15/lib/bundler.rb:173:in `require'
	from /app/config/application.rb:16:in `<top (required)>'
	from /app/config/environment.rb:2:in `require_relative'
	from /app/config/environment.rb:2:in `<top (required)>'
	from config.ru:3:in `require_relative'
	from config.ru:3:in `block in <main>'
	from /usr/local/bundle/gems/rack-2.2.3/lib/rack/builder.rb:116:in `eval'
	from /usr/local/bundle/gems/rack-2.2.3/lib/rack/builder.rb:116:in `new_from_string'
	from /usr/local/bundle/gems/rack-2.2.3/lib/rack/builder.rb:105:in `load_file'
	from /usr/local/bundle/gems/rack-2.2.3/lib/rack/builder.rb:66:in `parse_file'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/configuration.rb:345:in `load_rackup'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/configuration.rb:267:in `app'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/runner.rb:149:in `load_and_bind'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/single.rb:44:in `run'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/launcher.rb:181:in `run'
	from /usr/local/bundle/gems/puma-5.5.2/lib/puma/cli.rb:80:in `run'
	from /usr/local/bundle/gems/puma-5.5.2/bin/puma:10:in `<top (required)>'
	from /usr/local/bundle/bin/puma:23:in `load'
	from /usr/local/bundle/bin/puma:23:in `<main>'

@sfewings
Copy link

"sudo apt-get upgrade" to SMP Debian 4.19.235-1 worked for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants