Running GUI application with docker

Có lẽ bạn đã đang dùng docker để phát triển app rồi. Docker còn được dùng trong cả các server production nữa. Nói chung docker thường được coi là một công cụ phát triển hay để deploy app. Nhưng bạn có nghĩ những ưu điểm của docker cũng có thể phát huy ở một chỗ khác. Mình đang nói đến GUI app. Từ browser, text editor đến video call .etc hay thậm chí là game.

Why would I do that

Tại sao lại phải dùng docker? Cứ cài nó như bình thường không phải dễ hơn sao. Thật ra thì dùng docker cũng không khó khăn gì. Nó còn có vài lợi ích thú vị mà chắc bạn đã biết rồi.

Đầu tiên là tính portable. Cái này thì chắc không hề mới. Bạn có thể tiết kiệm được kha khá thời gian config mỗi lần cài mới app lên máy. Chỉ cần đơn giản chạy docker run. Thêm nữa bạn hoàn toàn kiểm soát ứng dụng mà bạn cài. Không cần phải lo sau khi xóa nó đi thì nó còn để lại một đống file không mong muốn hay mấy cái config giấu ở tít xó nào đấy mà chẳng thể nào tìm ra nữa. Khi bạn muốn xóa đi cài lại thì cũng dễ dàng hơn nhiều (lol). Hay ho hơn là bạn còn kiểm soát được hoạt động của container dễ dàng với rất nhiều option của docker. Muốn kiểm soát lượng RAM tối đa hay số cpu sử dụng chẳng hạn.

Bạn có thể chạy nhiều container của cùng một app. Nghĩa là bạn có thể có nhiều version của cùng một ứng dụng trên máy. Chạy nhiều version khác nhau của Chrome hay Firefox khá là khó khăn. Mỗi cái lại phải config lằng nhằng khác nhau. Còn phải để ý tắt autoupdate của nó nữa. Lúc cài xong rồi muốn chạy nó cũng không phải chỉ cần click là xong. Với docker thì mọi thứ trở nên đơn giản rồi.

How to do it

Làm thế nào mà GUI app được render. GUI app được render bởi X server (e.g xvfb). Mỗi GUI app là một X client. GUI app sẽ gửi các render instruction đến X server qua X11 socket. Vậy chúng ta có thể chạy GUI app trong docker bằng 2 cách.

Cách thứ nhất là để docker container render GUI app, sau đó stream hình ảnh từ container (như kiểu remote desktop ấy). Bạn sẽ phải cài X server và VNC server cho docker container. Cách thứ hai là share X11 socket với container và để X server trên máy host render. Mình cũng chưa thử cách 1 bao giờ và nó cũng có vẻ khó nên chúng ta hãy cùng làm theo cách thứ hai nhé. Chúng ta sẽ thử một ví dụ với Google Chrome.

Đầu tiên tạo một image đơn giản với Chrome đã.

FROM ubuntu:latest

# Install Chrome
RUN
    curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add - && \
    echo "deb [arch=amd64] https://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 && \
    rm -rf /var/lib/apt/lists/* && \
    rm -rf /src/*.deb

# Run Chrome when start with docker run
ENTRYPOINT [ "google-chrome" ]
CMD [ "--user-data-dir=/data" ]

Dockerfile này sẽ cài bản mới nhất của Google Chrome. Nếu bạn không muốn cài bản mới nhất mà muốn cài một bản khác, nếu bạn có file .deb thì có thể làm thế này.

FROM ubuntu:latest

# Install Chrome
RUN apt-get update
ADD chrome-other-version.deb .
RUN apt install -y ./chrome-other-version.deb

# Run Chrome when start with docker run
ENTRYPOINT [ "google-chrome" ]
CMD [ "--user-data-dir=/data" ]

Dockerfile không có gì đặc biệt. Tất nhiên nếu thích bạn có thể thêm file config, thêm extension, .etc. Nếu thích thì bạn có thể dùng hẳn docker-compose luôn cũng được.

Build image

docker build -t chrome

Bây giờ thì làm sao để chạy cái này. Bạn cần mount volume của file X11 socket tới file X11 socker của container và set biến env DISPLAY cho container. Xong xuôi rồi GUI app sẽ gửi render instruction tới thẳng X server trên máy host. Vậy câu lệnh để chạy docker container sẽ thế này

docker run -d \
    -e DISPLAY=$DISPLAY \           # the DISPLAY env variable
    -e XAUTHORITY=$XAUTHORITY \     # the XAUTHORITY env variable
    -v $XAUTHORITY:$XAUTHORITY \    # Mount the X11 socket
    chrome

Vậy thôi, script đó là đủ để chạy (chắc là) hầu hết app GUI với docker rồi. Bạn nên save nó thành 1 file để đỡ phải gõ đi gõ lại cái đống này.

Ngoài mấy cái trên, có thể bạn cũng cần thêm mấy option khác nữa

Đặt tên cho dễ nhận ra.

docker run -d \
    -e DISPLAY=$DISPLAY \           # the DISPLAY env variable
    -e XAUTHORITY=$XAUTHORITY \     # the XAUTHORITY env variable
    -v $XAUTHORITY:$XAUTHORITY \    # Mount the X11 socket
    --name chrome \
    chrome

Nếu bạn muốn có âm thanh từ container.

docker run -d \
    -e DISPLAY=$DISPLAY \           # The DISPLAY env variable
    -e XAUTHORITY=$XAUTHORITY \     # The XAUTHORITY env variable
    -v $XAUTHORITY:$XAUTHORITY \    # Mount the X11 socket
    --device /dev/snd \             # If you want sound
    chrome

Có một số app lại cần có một app khác mới chạy được. Như Skype cần có pulseaudio chẳng hạn. Bạn cũng có thể chạy pulseaudio ở hẳn 1 container khác.

docker run -d \
    -e DISPLAY=$DISPLAY \           # The DISPLAY env variable
    -e XAUTHORITY=$XAUTHORITY \     # The XAUTHORITY env variable
    -v $XAUTHORITY:$XAUTHORITY \    # Mount the X11 socket
    --link pulseaudio:pulseaudio \  # Link pulseaudio container
    -e PULSE_SERVER=pulseaudio \
    chrome

Với 1 số app có thể bạn sẽ muốn lưu lại state của nó mỗi lần tắt container đi và bật lại. Ví dụ như Chrome chẳng hạn, bạn sẽ muốn lưu lại history, download, config .etc.

docker run -d \
    -e DISPLAY=$DISPLAY \                  # The DISPLAY env variable
    -e XAUTHORITY=$XAUTHORITY \            # The XAUTHORITY env variable
    -v $XAUTHORITY:$XAUTHORITY \           # Mount the X11 socket
    -v $HOME/Downloads:~/Downloads \       # Keep state
    -v $HOME/.config/google-chrome/:~/config
    chrome

Bạn cũng có thể dùng native network cho container thay vì docker bridge để có tốc độ tốt hơn.

docker run -d \
    -e DISPLAY=$DISPLAY \           # The DISPLAY env variable
    -e XAUTHORITY=$XAUTHORITY \     # The XAUTHORITY env variable
    -v $XAUTHORITY:$XAUTHORITY \    # Mount the X11 socket
    --network="host" \              # Use native network
    chrome

Và còn rất nhiều option khác để bạn kiểm soát container nữa. Chúc các bạn nghịch vui vẻ với docker 😃