DEV Community

Weerasak Chongnguluam
Weerasak Chongnguluam

Posted on

Docker run ภายใต้ network ของ container อื่น

กำลังลองเล่น package chromedp https://github.com/chromedp/chromedp ซึ่งเป็น package ช่วยให้เราเขียน Go ไปต่อกับ Chrome Engine แล้วทำอะไรต่างๆได้แบบ browser ทำ โดยผ่านการเขียนโปรแกรมเอง ประโยชน์ก็ใช้สำหรับทำ web bot ทำ web crawler/scrapper ต่างๆนั่นเอง

ประเด็นของโพสต์นี้คือ กำลังพยายามเอามันไปรันภายใต้ Docker ซึ่งไม่มี Chrome อยู่ในนั้น อย่างไรก็ตาม Chrome มีวิธีให้เชื่อมต่อหามันผ่าน remote โดยใช้ Chrome DevTools Websocket endpoint

ประเด็นต่อมา เราต้องรัน Chrome headless ซึ่งเป็น Chrome engine แบบไม่ต้องติดตั้ง Chrome application นั่นเอง ดีที่มีคนทำ docker image เอาไว้แล้วที่นี่ https://hub.docker.com/r/chromedp/headless-shell/

ดังนั้นเราจะใช้งาน headless-shell ก็รันผ่าน docker แบบนี้

docker run -d -p 9222:9222 --rm --name headless-shell chromedp/headless-shell
Enter fullscreen mode Exit fullscreen mode

ต่อมาผมจะลองเล่นโค้ดตัวอย่างของ chromedp จากโค้ดนี้ https://github.com/chromedp/examples/blob/master/remote/main.go

// Command remote is a chromedp example demonstrating how to connect to an
// existing Chrome DevTools instance using a remote WebSocket URL.
package main

import (
    "context"
    "flag"
    "log"

    "github.com/chromedp/chromedp"
)

func main() {
    devtoolsWsURL := flag.String("devtools-ws-url", "", "DevTools WebSsocket URL")
    flag.Parse()
    if *devtoolsWsURL == "" {
        log.Fatal("must specify -devtools-ws-url")
    }

    // create allocator context for use with creating a browser context later
    allocatorContext, cancel := chromedp.NewRemoteAllocator(context.Background(), *devtoolsWsURL)
    defer cancel()

    // create context
    ctxt, cancel := chromedp.NewContext(allocatorContext)
    defer cancel()

    // run task list
    var body string
    if err := chromedp.Run(ctxt,
        chromedp.Navigate("https://duckduckgo.com"),
        chromedp.WaitVisible("#logo_homepage_link"),
        chromedp.OuterHTML("html", &body),
    ); err != nil {
        log.Fatalf("Failed getting body of duckduckgo.com: %v", err)
    }

    log.Println("Body of duckduckgo.com starts with:")
    log.Println(body[0:100])
}
Enter fullscreen mode Exit fullscreen mode

โดยเอามาใส่ go.mod เพื่อจัดการ package ให้ ซึ่ง go.mod มีโค้ดแบบนี้อยู่

module example-chromedp-remote

go 1.16

require github.com/chromedp/chromedp v0.7.1
Enter fullscreen mode Exit fullscreen mode

เสร็จแล้วก็สร้าง Dockerfile ให้มันแบบนี้

FROM golang:1.16.4-alpine as builder

WORKDIR /app

COPY go.mod /app
COPY main.go /app

RUN go mod download
RUN go build -o app

FROM alpine
COPY --from=builder /app/app /app
ENTRYPOINT ["/app"]
Enter fullscreen mode Exit fullscreen mode

จากนั้นทำการ build docker ด้วยคำสั่ง

docker build -t example-chromedp-remote .
Enter fullscreen mode Exit fullscreen mode

สุดท้าย เราจะรัน example-chromedp-remote โดยส่ง ws url ของ headless ไปเป็น option ให้มัน ส่วนวิธีหา ws url นั้นทำแบบนี้

curl 127.0.0.1:9222/json/version
{
   "Browser": "Chrome/90.0.4430.93",
   "Protocol-Version": "1.3",
   "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36",
   "V8-Version": "9.0.257.23",
   "WebKit-Version": "537.36 (@4df112c29cfe9a2c69b14195c0275faed4e997a7)",
   "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/browser/68424ed8-a10a-4633-9618-f93b443e0aa9"
}
Enter fullscreen mode Exit fullscreen mode

เราจะเอา URL ตรง "webSocketDebuggerUrl" ไปใช้งานนั่นเอง

ต่อไปก็สั่งรัน example-chromedp-remote แบบนี้

docker run --rm --network="container:headless-shell" example-chromedp-remote -devtools-ws-url="ws://127.0.0.1:9222/devtools/browser/68424ed8-a10a-4633-9618-f93b443e0aa9"
Enter fullscreen mode Exit fullscreen mode

จุดที่ทำให้รันโดยเชื่อมต่อไปให้มันเป็น network เดียวกัน IP เดียวกันได้คือ option --network="container:headless-shell" นั่นเอง pattern มันคือมี container: ด้านหน้า ตามด้วยชื่อ container ที่จะเอาไปเชื่อมต่อ (ref: https://docs.docker.com/engine/reference/run/#network-container)

แถมท้าย ถ้าใช้ docker compose แทนที่จะรันเองตรงๆนั้น จะใช้ config key ที่ชื่อ network_mode แต่ว่าแทนที่จะกำหนดชื่อ container ตรงๆ เราใช้ชื่อ service แทนได้โดยใช้ prefix service: ตามด้วยชื่อ service ตัวอย่างเช่น

---
version: "3.8"
services:

  chrome-headless:
    image: "chromedp/headless-shell"
    ports:
    - "9222:9222"

  example-chromedp-remote:
    build:
      context: .
    network_mode: "service:chrome-headless"
Enter fullscreen mode Exit fullscreen mode

จากนั้นก็สั่งรัน chrome-headless ผ่าน docker compose แบบนี้

docker compose up -d chrome-headless
Enter fullscreen mode Exit fullscreen mode

แล้วก็รัน example-chromedp-remote แบบนี้

docker compose run --rm example-chromedp-remote -devtools-ws-url="ws://127.0.0.1:9222/devtools/browser/cdaa6d7f-3d08-4593-ad4d-0d630abcd627"
Enter fullscreen mode Exit fullscreen mode

สรุป

ถ้าจะเชื่อมต่อไปใช้ network ของ container อื่นๆ ผ่าน docker run ให้ใช้ option --network ค่าที่กำหนดคือ container: ตามด้วยชื่อ container ที่จะไปต่อ เช่น --network="conatiner:headless"

แต่ถ้าใช้ docker compose ให้กำหนดโดยคีย์ที่ชื่อว่า network_mode ค่าที่กำหนดคือ service: ตามด้วยชื่อ service ที่จะไปต่อ เช่น network_mode: "service:chrome-headless"

ขอฝาก Buy Me a Coffee

สำหรับท่านใดที่อ่านแล้วชอบโพสต์ต่างๆของผมที่นี่ ต้องการสนับสนุนค่ากาแฟเล็กๆน้อยๆ สามารถสนับสนุนผมได้ผ่านทาง Buy Me a Coffee คลิ๊กที่รูปด้านล่างนี้ได้เลยครับ

Buy Me A Coffee

ส่วนท่านใดไม่สะดวกใช้บัตรเครดิต หรือ Paypal สามารถสนับสนุนผมได้ผ่านทาง PromptPay โดยดู QR Code ได้จากโพสต์ที่พินเอาไว้ได้ที่ Page DevDose ครับ https://web.facebook.com/devdoseth

Latest comments (0)