I have a Docker image which contains JRE, some Java web application and jmxterm
. The latter is used for running some ad-hoc administrative tasks. The image is used on the CentOS 7 server with Docker 1.13 (which is pretty old but is the latest version which is supplied via the distro’s repository) to run the web application itself.
All works well, but after updating jmxterm
from 1.0.0 to the latest version (1.0.2), I get the following warning when entering the running container and starting jmxterm
:
WARNING: Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)
After this, jmxterm
does not react to arrow keys (when trying to navigate through the command history), nor does it provide autocompletion.
Some quick investigation shows that the problem may be reproduced in the clean environment with CentOS 7. Say, this is how I could bootstrap the system and the container with all stuff I need:
$ vagrant init centos/7
$ vagrant up
$ vagrant ssh
[vagrant@localhost ~]$ sudo yum install docker
[vagrant@localhost ~]$ sudo systemctl start docker
[vagrant@localhost ~]$ sudo docker run -it --entrypoint bash openjdk:11
root@0c4c614de0ee:/# wget https://github.com/jiaqi/jmxterm/releases/download/v1.0.2/jmxterm-1.0.2-uber.jar
And this is how I enter the container and run jmxterm
:
[vagrant@localhost ~]$ sudo docker exec -it 0c4c614de0ee sh
root@0c4c614de0ee:/# java -jar jmxterm-1.0.2-uber.jar
WARNING: Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)
root@0c4c614de0ee:/# bea<TAB>
<Nothing happens, but autocompletion had to appear>
Few observations:
- the problem does not appear with older
jmxterm
no matter which image do I use; - the problem arises with new
jmxterm
no matter which image do I use; - the problem is not reproducible on my laptop (which has newer kernel and Docker);
- the problem is not reproducible if I use latest Docker (from the external repo) on the CentOS 7 server instead of CentOS 7’s native version 1.13.
What happens, and why the error is reproducible only in specific environments? Is there any workaround for this?
2
Answers
TLDR: running new
jmxterm
versions asjava -jar jmxterm-1.0.2-uber.jar < /dev/tty
is a quick, dirty and hacky workaround for having the autocompletion and other stuff work inside the interactive container session.A quick check shows that
jmxterm
tries to determine the terminal device used by the process — probably to obtain the terminal capabilities later — by running thetty
utility:The utility fails with the status of 1, which is likely the reason for the error message. Why?
The utility says "not a tty" while we definitely have one. A quick check shows that... There is really no PTY device in the container though the standard streams of the shell are connected to one!
What if we check the same with latest Docker?
Bingo! Now we have our PTYs where they should be, so
jmxterm
works well with latest Docker.It seems pretty weird that with older Docker the processes are connected to some PTYs while there are no devices for them in
/dev/pts
, but tracing the Docker process explains why this happens. Older Docker allocates the PTY for the container before setting other things up (including entering the new mount namespace and mountingdevpts
into it or just entering the mount namespace in case ofdocker exec -it
):Note the
newinstance
mount option which ensures that thedevpts
mount owns its PTYs exclusively and does not share them with other mounts. This leads to the interesting effect: the PTY device for the container stays on the host and belongs to the host'sdevpts
mount, while the containerized process still has access to it, as it obtained the already-open file descriptors just in the beginning of its life!The latest Docker first mounts
devpts
for the container and then allocates the PTY, so the PTY belongs to container'sdevpts
mount and is visible inside the container's filesystem:Well, the problem is caused by inappropriate Docker behavior, but how comes that older
jmxterm
worked well in the same environment? Let's check (note, that Java 8 image is used here, as olderjmxterm
does not play well with Java 11):So, older
jmxterm
just uses/dev/tty
instead of askingtty
for the device name, and this works, as this device is present inside the container:The huge difference between these versions of
jmxterm
is that newer tool version uses higher major version ofjline
, which is the library responsible for interaction with the terminal (akin to thereadline
in the C world). The difference between majorjline
versions leads to the difference injmxterm
's behavior, and current versions just rely ontty
.This observation leads us to the quick and dirty workaround which does not require neither updating Docker nor patching the
jline
/jmxterm
tandem: we may just attachjmxterm
's stdin to/dev/tty
forcibly and thus makejline
use this device (which is now referenced by/proc/self/fd/0
) instead of the/dev/pts
entry (which, formally, is not always correct, but is still enough for ad-hoc use):Now we have the autocompletion, history and other cool things we need to have.
If you are trying to run an interactive application (that needs tty) inside a docker container or a pod in kubernetes, then the following should work.
For docker-compose use:
For kubernetes use: