From 45eb5c28a74ba71cd99462b3967e233d2a395fb5 Mon Sep 17 00:00:00 2001 From: mr c0b Date: Sat, 13 Aug 2016 23:34:51 -0700 Subject: [PATCH] google chrome in docker: initial commit --- Dockerfile | 35 +++++++ README.md | 232 +++++++++++++++++++++++++++++++++++++++++++ entry.sh | 20 ++++ get-latest-chrome.sh | 14 +++ supervisord.conf | 27 +++++ 5 files changed, 328 insertions(+) create mode 100644 Dockerfile create mode 100644 README.md create mode 100755 entry.sh create mode 100755 get-latest-chrome.sh create mode 100644 supervisord.conf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e15a250 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ + +FROM ubuntu + +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -xe \ + && apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl socat \ + && apt-get install -y --no-install-recommends xvfb x11vnc fluxbox xterm \ + && apt-get install -y --no-install-recommends sudo \ + && apt-get install -y --no-install-recommends supervisor \ + && rm -rf /var/lib/apt/lists/* + +RUN set -xe \ + && curl -fsSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ + && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \ + && apt-get update \ + && apt-get install -y google-chrome-stable \ + && rm -rf /var/lib/apt/lists/* + +#======================================== +# Add normal user with passwordless sudo +#======================================== +RUN set -xe \ + && useradd -u 1000 -g 100 -G sudo --shell /bin/bash --no-create-home --home-dir /tmp user \ + && echo 'ALL ALL = (ALL:ALL) NOPASSWD: ALL' >> /etc/sudoers + +COPY supervisord.conf /etc/ +COPY entry.sh / + +User user +WORKDIR /tmp +VOLUME /tmp/chrome-data + +CMD ["/entry.sh"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d05575 --- /dev/null +++ b/README.md @@ -0,0 +1,232 @@ + +# google-chrome in docker + +*Need to test some latest Chrome version's features?* but hestitant to +upgrade your main browser to unstable? this chrome-in-docker project can help you + +## Features + +- It downloads a google-chrome Linux version from chrome channels, either + stable, or beta, or developer version; install and pack into a docker + container, that can run on anywhere you have docker daemon; + https://www.chromium.org/getting-involved/dev-channel#TOC-Linux + +- It turns google-chrome into a headless browser, can be used together + with Selenium with chrome webdriver, or with Chrome's native Remote + Debugging Protocol you can program with + https://developer.chrome.com/devtools/docs/debugger-protocol + https://github.com/cyrus-and/chrome-remote-interface + that makes it a better headless browser than PhantomJS or SlimerJS, + better programability in my opinion; + while if need debugging, you have a VNC session to see the actual browser, + and do whatever you want, or you can even use it as your everyday main browser. + +# Usage + +You may either just pull my prebuilt docker image at https://hub.docker.com/r/c0b0/chrome-stable/ + + $ docker pull c0b0/chrome-stable + $ docker run -it --rm c0b0/chrome-stable /opt/google/chrome/google-chrome --version + Google Chrome 52.0.2743.116 + +Or build it locally with Dockerfile here + + $ docker build -t chrome-stable:20160813 . + +Check what Chrome version is builtin, and tag a version: + + $ docker run -it --rm chrome-stable:20160813 /opt/google/chrome/google-chrome --version + Google Chrome 52.0.2743.116 + $ docker tag chrome-stable:20160813 chrome-stable:52.0.2743.116 + +The extra `get-latest-chrome.sh` script here is to get latest versions of +Chrome Stable, Beta, or Unstable version, for testing some latest features, +here you may modify the Dockerfile to build a different image with each one, +while, since the beta and unstable versions are changing fast, may be updating +every week or every day, you don't have to rebuild docker images everyday, +with this `get-latest-chrome.sh` and local volume bind, you can run a different +container with the same image; that way, within a relatively longer time range +you don't have to rebuild the base docker image; the reasons of a same base image +can be reused is dependencies of the different channels (stable, beta, or -dev) +are most probably the same, or changing much less often; anyway, if there is +any problem that stable can run but unstable cannot, you may always have a no-cache +rebuild: by `docker build --pull --no-cache ...` to force pull latest ubuntu base +and latest Chrome binary packages. + + $ ./get-latest-chrome.sh + [... downloading latest Chrome and extracting to ./opt ...] + +You may test run it one time first to check what's exact version of each Chrome channel: + + $ docker run -it --rm -v $PWD/opt:/opt:ro chrome:20160813 \ + /opt/google/chrome-unstable/google-chrome-unstable --version + Google Chrome 54.0.2824.0 dev + + $ docker run -it --rm -v $PWD/opt:/opt:ro chrome:20160813 \ + /opt/google/chrome-beta/google-chrome-beta --version + Google Chrome 53.0.2785.57 beta + + $ docker run -it --rm -v $PWD/opt:/opt:ro chrome:20160813 \ + /opt/google/chrome/google-chrome --version + Google Chrome 52.0.2743.116 + +Then run 3 different containers with the same base docker image: + +```console +$ docker run -dt \ + --name Chrome-dev-54.0.2824.0 \ + -h chrome-dev-54.local \ + -v $PWD/opt:/opt:ro \ + -e CHROME=/opt/google/chrome-unstable/google-chrome-unstable \ + chrome:20160813 +56417156ffea4a55642cfa59cf5e9758a2be144144b2df39e91aa9265f098b75 +$ docker run -dt \ + --name Chrome-beta-53.0.2785.57 \ + -h chrome-beta-53.local \ + -v $PWD/opt:/opt:ro \ + -e CHROME=/opt/google/chrome-beta/google-chrome-beta \ + chrome:20160813 +d5b784cbe9ac7d3a52b43c7fb6918b28366c8b939293b10fb9b1808de7b46e2e +$ docker run -dt \ + --name Chrome-stable-52.0.2743.116 \ + -h chrome-beta-52.local \ + -v $PWD/opt:/opt:ro \ + chrome:20160813 +35974a5247cf8650da25d03d9f279749ae4cf1e5b0c57349af1d511b8ac99545 + +$ docker ps -a +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +35974a5247cf chrome:20160813 "/entry.sh" ... Chrome-stable-52.0.2743.116 +d5b784cbe9ac chrome:20160813 "/entry.sh" ... Chrome-beta-53.0.2785.57 +56417156ffea chrome:20160813 "/entry.sh" ... Chrome-dev-54.0.2824.0 +``` + +To connect the chrome in docker, you may either use port mappings, let it call proper +iptables to set up proper mappings; or use inspect to find out the ip addresses +of each container: + + $ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' Chrome-dev-54.0.2824.0 + 172.18.0.4 + +That means the chrome browser's Chrome Debugging Protocol can be accessed by `172.18.0.4:9222` + + $ curl -s 172.18.0.4:9222/json/version + { + "Browser": "Chrome/54.0.2824.0", + "Protocol-Version": "1.1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2824.0 Safari/537.36", + "WebKit-Version": "537.36 (@facabd3224aecbcab4bea9daadad31c67488d78c)" + } + +Or, if you use docker port mapping, like: + +```console + # this one is not using any local volume binding on /opt, so it's using the builtin Chrome at build time, +$ docker run -dt \ + --name Chrome-stable-builtin-52.0.2743.116 \ + -h chrome-stable-52.local \ + -p 9222:9222 \ + chrome:20160813 +e9a3738f2d642e5d1a4dd895750d1a09ddece3dd187c82309ade99e1b4123027 +$ docker ps -a +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +e9a3738f2d64 chrome:20160813 "/entry.sh" 3 seconds ago Up 3 seconds 0.0.0.0:9222->9222/tcp Chrome-stable-builtin-52.0.2743.116 + + # by inspect we know we can access this container by 172.18.0.2:9222 +$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' Chrome-stable-builtin-52.0.2743.116 +172.18.0.2 +$ curl -s 172.18.0.2:9222/json/version +{ + "Browser": "Chrome/52.0.2743.116", + "Protocol-Version": "1.1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "WebKit-Version": "537.36 (@9115ecad1cae66fd5fe52bd9120af643384fd6f3)" +} + # by above port mapping, this container can also be accessed by 0.0.0.0:9222; if it's from localhost Linux, +$ curl -s localhost:9222/json/version +{ + "Browser": "Chrome/52.0.2743.116", + "Protocol-Version": "1.1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "WebKit-Version": "537.36 (@9115ecad1cae66fd5fe52bd9120af643384fd6f3)" +} +``` + +You may try https://github.com/cyrus-and/chrome-har-capturer with more har capturing commands +like `chrome-har-capturer -t 172.18.0.2 urls...` + +## Debugging + +VNC session listens default on the container's 5900 port, if you figured out the container's +IP address (by above inspect command), an VNC session can be opened by your favorite +VNC client connect to this ip address, or you may use another `-p localport:5900` +to set up another port forwarding to be able to use it from a 3rd computer. + +## Env variables to customize + +1. the default VNC password is `hola`; you may pass additional env var to docker run + by `-e VNC_PASSWORD=xxx` to change to use a different VNC password; +2. the default CHROME is `/opt/google/chrome/google-chrome`, if you use local + volume bind to have different chrome versions, you may pass additional env var + by `-e CHROME=/path/to/chrome or chromium` + +# Design + +## Docker Image Build Time + +1. The Dockerfile defined process of where as start, it's starting from latest + +2. Ubuntu as base image, then install VNC and some network utilties like curl and socat, +xvfb, x11vnc as Graphic layer for Chrome graphical output, xterm as debugging term window +supervisor as processes manager, sudo also for debugging, not technically required. + +3. Then add Google-Chrome's apt source and install google-chrome-stable version, +and it will handle all runtime dependencies by Chrome; +This static version will be packed as part of the docker image, when you're not +using local volume bind, this version will be used. It depends how often do you +rebuild, but with above `./get-latest-chrome.sh` script, you don't have to rebuild +very often. + +3. Then add a regular user at 1000:100 for improved security and run all services +under this regular user; sudo can be used for debugging. +Copying supervisord.conf as definition of process structure; and entry.sh as +container entrypoint. + +## Container Spawn +At container spawn time (`docker run ...`), it starts from the entrypoint `entry.sh` +there it handles default VNC password `hola`, and check CHROME environment, +set it default to the stable version `/opt/google/chrome/google-chrome`; + +Then it exec to supervisord to spawn more processes defined in `supervisord.conf` + +## Process Management + +Supervisord is the process manager, it spawns 4 processes: + +1. Xvfb ... as X server +2. x11vnc ... as VNC on top of X Server +3. fluxbox as window manager, this is technically not required, + any X11 application can directly run on X server, but with a window + manager, it's easier for debugging, when need to move window, resize, + maximize, and minimize, etc. +4. xterm, same for debugging +5. start chrome from CHROME environment variable, with `--remote-debugging-port=19222` + to enable Remote Debugging Protocol +4. socat, as a forwarding channel, chrome can only listen on local loopback + interface (127.0.0.1); hence not accepting any request from outside + so a tcp forwarding tool like socat is necessary here. + +Supervisord will respawn any managed processes if it crashed. + +Ideally here should define dependencies between the processes, but due to +https://github.com/Supervisor/supervisor/issues/122 it lacks such feature. + +# Some further improvements + +- [ ] Chromium nightly https://download-chromium.appspot.com/ +- [ ] VNC in browser, see https://github.com/fcwu/docker-ubuntu-vnc-desktop + have an openbox version, or lxde, an lightweight also full featured + Ubuntu desktop +- [ ] setup iptables instead of socat +- [ ] find replacement of supervisord, need a lightweight mananger also has + dependencies management. But sysvinit, upstart, or systemd is too heavy. \ No newline at end of file diff --git a/entry.sh b/entry.sh new file mode 100755 index 0000000..976b98d --- /dev/null +++ b/entry.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -xe + +VNC_STORE_PWD_FILE=~/.vnc/passwd +if [ ! -e "${VNC_STORE_PWD_FILE}" -o -n "${VNC_PASSWORD}" ]; then + mkdir -vp ~/.vnc + + # the default VNC password is 'hola' + x11vnc -storepasswd ${VNC_PASSWORD:-hola} ${VNC_STORE_PWD_FILE} +fi + +# default CHROME is the stable version +export CHROME=${CHROME:-/opt/google/chrome/google-chrome} + +# make the new volume owned by regular user +sudo chown -Rv 1000:100 /tmp/chrome-data + +# retain running as pid 1 +exec supervisord \ No newline at end of file diff --git a/get-latest-chrome.sh b/get-latest-chrome.sh new file mode 100755 index 0000000..f3a1dcf --- /dev/null +++ b/get-latest-chrome.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# delete previously versions +\rm -rf dl.google.com opt + +# download latest unstable, beta, & stable chrome versions +wget -mS https://dl.google.com/linux/direct/google-chrome-{unstable,beta,stable}_current_amd64.deb + +# on Debian/Ubuntu based systems, `dpkg --fsys-tarfile` can also be used to extract deb file contents, +# but ar is more generic, and availalbe on all Linux variants +ar p dl.google.com/linux/direct/google-chrome-unstable_current_amd64.deb data.tar.xz | tar --xz -xvv ./opt/google/chrome-unstable/ +ar p dl.google.com/linux/direct/google-chrome-beta_current_amd64.deb data.tar.xz | tar --xz -xvv ./opt/google/chrome-beta/ +ar p dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb data.tar.xz | tar --xz -xvv ./opt/google/chrome/ + diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..bf1f6f8 --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,27 @@ + +[supervisord] +nodaemon=true + +[program:xvfb] +command=Xvfb :10 -screen 0 1920x1480x24+32 -ac -r -cc 4 -accessx -xinerama +extension Composite -extension RANDR +extension GLX + +[program:x11vnc] +command=x11vnc -rfbport 5900 -display :10 -rfbauth /tmp/.vnc/passwd -forever -shared + +[program:fluxbox] +command=fluxbox -display :10 + +[program:xterm] +command=xterm -display :10 + +[program:chrome] +command=%(ENV_CHROME)s --no-first-run + --user-data-dir=./chrome-data + --force-device-scale-factor=2 + --remote-debugging-port=19222 + --enable-benchmarking + --enable-net-benchmarking +environment=DISPLAY=":10" + +[program:socat] +command=/usr/bin/socat -v -d tcp-listen:9222,reuseaddr,fork tcp:localhost:19222 \ No newline at end of file