やりたいこと
- PlaywrightまたはPuppeteerを使って、ブラウザ操作してクローリングしたい。
- そのブラウザは、サーバーやクラウドで動かしたい。
- 本記事では、ブラウザはサーバー/クラウドで、Puppeteer/Playwrightはローカルで動かす構成を目指します。
- サーバーで動くブラウザを、クライアントでも人が見て操作したい(reCAPTCHA突破等)。
構成図
試行錯誤の結果、最終的な構成は以下の図のようになりました。
おおまかなイメージ図です。等幅フォントで見てね。
┏━━━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━┓
┃ サーバー/クラウドのDockerコンテナ ┃ ┃ クライアント ┃
┃ ┏━━┻━━━┓ ┃ ┃
┃┌──────┐ ┌────┐PORT ┃ ┃ PORT ┌─────┐┃
┃│Xvfb │←─┤Vnc │←──╂──────╂─╂─┤ Vnc │┃
┃│ 仮想Display├─→│ Server├───╂──────╂─╂→│ Viewer │┃
┃└───┬──┘ └────┘5900 ┃ ┃ 5900 └─────┘┃
┃ ↑│ ┃ SSH ┃ ┃ ┃
┃ │↓ ┃Portforward ┃ ┃ ┃
┃┌──┴───┐PORT┌────┐PORT ┃ ┃ PORT ┌─────┐┃
┃│ Google │←─┤Reverse │←──╂──────╂─╂─┤Playwright│┃
┃│ Chrome ├─→│ Proxy ├───╂──────╂─╂→│/Puppeteer│┃
┃└──────┘9222└────┘19222 ┃ ┃ 9222 └─────┘┃
┃ (Remote Debugging Port) ┗━━┳━━━┛ ┃ ┃
┃ ┃ ┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━┛
ただし、 本記事ではDockerコンテナもローカルで動かすにとどめています。
Dockerfile作成
Step 1:Google Chromeをインストール
このページのDockerfileをアレンジしました(感謝)。
変更点として、DockerでPuppeteerは動かしませんので、nodeも入れないようにしました。
作成中のDockerfile
FROM debian:bullseye
RUN apt-get update \
&& apt-get install -y wget gnupg \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update \
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
Step 2:Xvfb(仮想Display)とVNCサーバーをインストール
Google Chromeは--headlessオプションをつけずに起動し、Xvfb(仮想Display)に表示させることにします。
そのDisplay画面をクライアントで表示・操作したいので、リモートデスクトップ接続用にVNCサーバーもインストールします。
Dockerfileに以下のapt-get installを加えます。
&& apt-get install -y xauth xvfb x11vnc x11-apps \
Step 3:SSH Serverのインストールと設定
セキュリティーの理由により、SSHポートフォワーディングを使用します。
- 構成図の5900ポートと9222ポートが、SSHログインしたユーザーのみに開放されます。
- ポートを通じた通信が暗号化されます。
Dockerfileに以下のapt-get installを加えます。
&& apt-get install -y openssh-server \
また、DockerfileでSSHの設定も行います。
RUN mkdir /root/.ssh \
&& chmod 700 /root/.ssh
COPY ./id_ed25519.pub /root/.ssh/authorized_keys
RUN chmod 600 /root/.ssh/authorized_keys
COPY ./sshd_config /etc/ssh/
公開鍵(本記事では id_ed25519.pub)と設定ファイル(sshd_config)は事前に用意しておきます。
sshd_configは以下を設定します。
- 公開鍵認証を許可する:PubkeyAuthentication yes
- パスワード認証を禁止する:PasswordAuthentication no
- SSHポートをデフォルトから変える:Port 2222
- (本記事では設定しませんが必要に応じて)GatewayPorts clientspecified
参考までに、以下に sshd_config 全体を載せます。
# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $
# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.
# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.
Include /etc/ssh/sshd_config.d/*.conf
Port 2222
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key
# Ciphers and keying
#RekeyLimit default none
# Logging
#SyslogFacility AUTH
#LogLevel INFO
# Authentication:
#LoginGraceTime 2m
#PermitRootLogin prohibit-password
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
PubkeyAuthentication yes
# Expect .ssh/authorized_keys2 to be disregarded by default in future.
#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes
# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication no
#PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no
# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes
#AllowAgentForwarding yes
#AllowTcpForwarding yes
GatewayPorts no
X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
PrintMotd no
#PrintLastLog yes
#TCPKeepAlive yes
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none
# no default banner path
#Banner none
# Allow client to pass locale environment variables
AcceptEnv LANG LC_*
# override default of no subsystems
Subsystem sftp /usr/lib/openssh/sftp-server
# Example of overriding settings on a per-user basis
#Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server
Step 4:Reverse Proxyの設定
Google Chromeに--remote-debugging-port=9222オプションをつけてリモートデバッグ有効で起動し、9222ポートのエンドポイントに疎通させたいのですが、ここで問題が発生しました。
- Chromeに--headlessオプションをつけて起動すると、Dockerコンテナの外からも疎通する
- Chromeに--headlessオプションをつけずに起動すると、Dockerコンテナの外から疎通しない
セキュリティーの理由から、--headlessをつけないと外からの疎通を許さないようです。
本記事では、セキュリティーはSSHポートフォワーディングで担保されているとして、--headlessオプションをつけずに起動しても疎通するようにします。
そのために、構成図のようにリバースプロキシを導入します。
リバースプロキシのソースコードは以下の通りです(Go言語)。
- 参考サイト(感謝します): https://nnt339es.hatenablog.com/entry/2021/11/16/070340
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
loc := "/"
proxyPass := "http://localhost:9222"
url, err := url.Parse(proxyPass)
if err != nil {
log.Fatal(err)
}
http.Handle(loc, httputil.NewSingleHostReverseProxy(url))
http.ListenAndServe(":19222", nil)
}
そしてDockerfileの最初の方に以下を加えて、reverse_proxyを導入します。
FROM golang:bullseye as go-build
WORKDIR /app
COPY ./reverse_proxy.go ./
RUN go build -o reverse_proxy reverse_proxy.go
FROM debian:bullseye
WORKDIR /app
# リバースプロキシ設定
COPY --from=go-build /app/reverse_proxy /app/
最終的なDockerfile
まずDockerfile以外に、事前に以下のファイルを用意しました。
- id_ed25519.pub(前述)
- sshd_config(前述)
- reverse_proxy.go(前述)
- cmd.sh
cmd.shの内容は以下の通りです。
#!/bin/bash
set -eo pipefail
echo "service start."
service ssh start
service dbus start
echo "exec reverse_proxy."
exec /app/reverse_proxy &
echo "exec xvfb-run."
exec xvfb-run \
--auth-file /tmp/xvfb-run \
--server-args=":99 -screen 0 1024x768x24" \
google-chrome \
--no-sandbox \
--disable-dev-shm-usage \
--disable-gpu \
--no-first-run \
--no-default-browser-check \
--remote-debugging-address=0.0.0.0 \
--remote-debugging-port=9222 \
about:blank &
echo "exec x11vnc."
exec x11vnc -display :99 -auth /tmp/xvfb-run -forever &
# Exit immediately when one of the background processes terminate.
echo "wait."
wait -n
そして最終的なDockerfileは以下の通りです。
FROM golang:bullseye as go-build
WORKDIR /app
COPY ./reverse_proxy.go ./
RUN go build -o reverse_proxy reverse_proxy.go
FROM debian:bullseye
WORKDIR /app
# リバースプロキシ設定
COPY --from=go-build /app/reverse_proxy /app/
# Google Chrome、SSH Server、Xvfb、VNCのインストール
RUN apt-get update \
&& apt-get install -y wget gnupg \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update \
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
--no-install-recommends \
&& apt-get install -y openssh-server \
&& apt-get install -y xauth xvfb x11vnc x11-apps \
&& rm -rf /var/lib/apt/lists/*
# SSH設定
RUN mkdir /root/.ssh \
&& chmod 700 /root/.ssh
COPY ./id_ed25519.pub /root/.ssh/authorized_keys
RUN chmod 600 /root/.ssh/authorized_keys
COPY ./sshd_config /etc/ssh/
# コマンド起動
COPY ./cmd.sh ./
RUN chmod +x ./cmd.sh
CMD ["/app/cmd.sh"]
動かしてみる
docker buildします。
docker build -t chrome-linux .
docker runします。
docker run --publish 2222:2222 chrome-linux
SSHポートフォワーディングを有効にするオプションをつけて、sshでdockerコンテナにログインします。
ssh -o ExitOnForwardFailure=yes -p 2222 \
-L 9222:localhost:19222 -L 5900:localhost:5900 root@localhost
9222ポートへの疎通を確認します。
curl -I http://127.0.0.1:9222/json/list
以下のように200が返ればOKです。
HTTP/1.1 200 OK
Content-Length: 5
Content-Security-Policy: frame-ancestors 'none'
Content-Type: application/json; charset=UTF-8
Date: Sun, 19 Feb 2023 13:57:31 GMT
VNC Viewerを起動して、Dockerコンテナへのリモートデスクトップ接続を試みます。
本記事では TightVNC Viewerを使用します。
以下のように繋がりました。
リモートデスクトップ接続ですので、人がGoogle Chromeを操作できます。
このブラウザをPlaywrightから操作する場合は、launch()の代わりにconnectOverCDP()を呼びます。
const browser = await playwright.chromium.connectOverCDP("http://localhost:9222");
const context = browser.contexts()[0];
const page = await context.newPage();
このブラウザをPuppeteerから操作する場合は、launch()の代わりにconnect()を呼びます。
const browser = await puppeteer.connect({
browserURL: "http://localhost:9222",
});
const page = await browser.newPage();