-
Notifications
You must be signed in to change notification settings - Fork 5.6k
devops(docker): split browser layers and use zstd compression for faster pulls #40702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ce45856
b9c35ce
5c123f1
a053b1d
5f9f0b4
71fd7b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| name: "benchmark: docker pull time" | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
|
|
||
| env: | ||
| ELECTRON_SKIP_BINARY_DOWNLOAD: 1 | ||
|
|
||
| jobs: | ||
| benchmark: | ||
| name: "pull-time comparison" | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| packages: write | ||
| contents: read | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: 20 | ||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
| - name: Login to GHCR | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| - run: npm ci | ||
| - run: npm run build | ||
| - name: Build monolithic image (main branch Dockerfile) | ||
| run: | | ||
| node utils/pack_package.js playwright-core utils/docker/playwright-core.tar.gz | ||
| git show main:utils/docker/Dockerfile.jammy > /tmp/Dockerfile.monolithic | ||
| docker buildx build \ | ||
| --platform linux/amd64 \ | ||
| --push \ | ||
| -f /tmp/Dockerfile.monolithic \ | ||
| -t ghcr.io/${{ github.repository }}:bench-monolithic \ | ||
| utils/docker/ | ||
| - name: Build split-layer image (this branch Dockerfile) | ||
| run: | | ||
| docker buildx build \ | ||
| --platform linux/amd64 \ | ||
| --push \ | ||
| -f utils/docker/Dockerfile.jammy \ | ||
| -t ghcr.io/${{ github.repository }}:bench-split \ | ||
| utils/docker/ | ||
| - name: Benchmark cold pulls | ||
| run: | | ||
| echo "## Docker Pull Benchmark" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "Runner: $(uname -m), $(nproc) cores" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "| Image | Run 1 | Run 2 | Run 3 | Avg |" >> $GITHUB_STEP_SUMMARY | ||
| echo "|-------|-------|-------|-------|-----|" >> $GITHUB_STEP_SUMMARY | ||
|
|
||
| for variant in monolithic split; do | ||
| IMAGE="ghcr.io/${{ github.repository }}:bench-${variant}" | ||
| TIMES=() | ||
| for i in 1 2 3; do | ||
| docker rmi "$IMAGE" 2>/dev/null | ||
| docker image prune -a -f >/dev/null 2>&1 | ||
| START=$(date +%s%N) | ||
| docker pull "$IMAGE" >/dev/null 2>&1 | ||
| END=$(date +%s%N) | ||
| ELAPSED=$(python3 -c "print(f'{($END - $START) / 1e9:.1f}')") | ||
| TIMES+=("$ELAPSED") | ||
| done | ||
| AVG=$(python3 -c "t=[${TIMES[0]},${TIMES[1]},${TIMES[2]}]; print(f'{sum(t)/len(t):.1f}')") | ||
| echo "| ${variant} | ${TIMES[0]}s | ${TIMES[1]}s | ${TIMES[2]}s | ${AVG}s |" >> $GITHUB_STEP_SUMMARY | ||
| done | ||
|
|
||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "### Layer structure" >> $GITHUB_STEP_SUMMARY | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
| echo "Monolithic:" >> $GITHUB_STEP_SUMMARY | ||
| docker manifest inspect ghcr.io/${{ github.repository }}:bench-monolithic 2>/dev/null | \ | ||
| python3 -c " | ||
| import json,sys | ||
| data = json.load(sys.stdin) | ||
| for m in data.get('manifests', []): | ||
| if m['platform']['architecture'] == 'amd64': | ||
| print(f\" digest: {m['digest'][:32]}...\") | ||
| " || true | ||
| docker buildx imagetools inspect ghcr.io/${{ github.repository }}:bench-monolithic --raw 2>/dev/null | \ | ||
| python3 -c " | ||
| import json,sys | ||
| data = json.load(sys.stdin) | ||
| layers = data.get('layers', []) | ||
| for i,l in enumerate(layers): | ||
| print(f' Layer {i}: {l[\"size\"]/1e6:.1f} MB') | ||
| print(f' Total: {sum(l[\"size\"] for l in layers)/1e6:.1f} MB') | ||
| " >> $GITHUB_STEP_SUMMARY || true | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "Split:" >> $GITHUB_STEP_SUMMARY | ||
| docker buildx imagetools inspect ghcr.io/${{ github.repository }}:bench-split --raw 2>/dev/null | \ | ||
| python3 -c " | ||
| import json,sys | ||
| data = json.load(sys.stdin) | ||
| layers = data.get('layers', []) | ||
| for i,l in enumerate(layers): | ||
| print(f' Layer {i}: {l[\"size\"]/1e6:.1f} MB') | ||
| print(f' Total: {sum(l[\"size\"] for l in layers)/1e6:.1f} MB') | ||
| " >> $GITHUB_STEP_SUMMARY || true | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,8 @@ jobs: | |
| uses: docker/setup-qemu-action@v4 | ||
| with: | ||
| platforms: arm64 | ||
| - name: Set up Docker Buildx | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. revert |
||
| uses: docker/setup-buildx-action@v3 | ||
| - run: npm ci | ||
| - run: npm run build | ||
| - name: Azure Login | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| # syntax=docker/dockerfile:1 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. revert |
||
| FROM ubuntu:jammy | ||
|
|
||
| ARG DEBIAN_FRONTEND=noninteractive | ||
|
|
@@ -11,7 +12,6 @@ ENV LC_ALL=C.UTF-8 | |
| # === INSTALL Node.js === | ||
|
|
||
| RUN apt-get update && \ | ||
| # Install Node.js | ||
| apt-get install -y curl wget gpg ca-certificates && \ | ||
| mkdir -p /etc/apt/keyrings && \ | ||
| curl -sL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ | ||
|
|
@@ -27,23 +27,21 @@ RUN apt-get update && \ | |
| adduser pwuser | ||
|
|
||
| # === BAKE BROWSERS INTO IMAGE === | ||
| # Browsers are split into separate layers to enable parallel pulls. | ||
|
|
||
| ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright | ||
|
|
||
| # 1. Add tip-of-tree Playwright package to install its browsers. | ||
| # The package should be built beforehand from tip-of-tree Playwright. | ||
| COPY ./playwright-core.tar.gz /tmp/playwright-core.tar.gz | ||
|
|
||
| # 2. Bake in browsers & deps. | ||
| # Browsers will be downloaded in `/ms-playwright`. | ||
| # Note: make sure to set 777 to the registry so that any user can access | ||
| # registry. | ||
| # 2. Set up playwright-core and install all system dependencies in one layer. | ||
| RUN mkdir /ms-playwright && \ | ||
| mkdir /ms-playwright-agent && \ | ||
| cd /ms-playwright-agent && npm init -y && \ | ||
| npm i /tmp/playwright-core.tar.gz && \ | ||
| npm exec --no -- playwright-core mark-docker-image "${DOCKER_IMAGE_NAME_TEMPLATE}" && \ | ||
| npm exec --no -- playwright-core install --with-deps && rm -rf /var/lib/apt/lists/* && \ | ||
| npm exec --no -- playwright-core install-deps chromium chromium-headless-shell firefox webkit && \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not list browsers, install all deps. |
||
| # Workaround for https://github.com/microsoft/playwright/issues/27313 | ||
| # While the gstreamer plugin load process can be in-process, it ended up throwing | ||
| # an error that it can't have libsoup2 and libsoup3 in the same process because | ||
|
|
@@ -53,7 +51,25 @@ RUN mkdir /ms-playwright && \ | |
| else \ | ||
| rm /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstwebrtc.so; \ | ||
| fi && \ | ||
| rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # 3. Install each browser binary in its own layer for parallel pulling. | ||
| RUN cd /ms-playwright-agent && \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like you could benefit from splitting these too? chromium is your largest layer |
||
| npm exec --no -- playwright-core install chromium chromium-headless-shell && \ | ||
| chmod -R 777 /ms-playwright/chromium-* /ms-playwright/chromium_headless_shell-* | ||
|
|
||
| RUN cd /ms-playwright-agent && \ | ||
| npm exec --no -- playwright-core install firefox && \ | ||
| chmod -R 777 /ms-playwright/firefox-* | ||
|
|
||
| RUN cd /ms-playwright-agent && \ | ||
| npm exec --no -- playwright-core install webkit && \ | ||
| chmod -R 777 /ms-playwright/webkit-* | ||
|
|
||
| # 4. Install ffmpeg and clean up. | ||
| RUN cd /ms-playwright-agent && \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First installed browser will do it, this layer will be empty. |
||
| npm exec --no -- playwright-core install ffmpeg && \ | ||
| rm /tmp/playwright-core.tar.gz && \ | ||
| rm -rf /ms-playwright-agent && \ | ||
| rm -rf ~/.npm/ && \ | ||
| chmod -R 777 /ms-playwright | ||
| chmod -R 777 /ms-playwright/ffmpeg-* | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| # syntax=docker/dockerfile:1 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dtop |
||
| FROM ubuntu:noble | ||
|
|
||
| ARG DEBIAN_FRONTEND=noninteractive | ||
|
|
@@ -11,7 +12,6 @@ ENV LC_ALL=C.UTF-8 | |
| # === INSTALL Node.js === | ||
|
|
||
| RUN apt-get update && \ | ||
| # Install Node.js | ||
| apt-get install -y curl wget gpg ca-certificates && \ | ||
| mkdir -p /etc/apt/keyrings && \ | ||
| curl -sL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ | ||
|
|
@@ -27,24 +27,40 @@ RUN apt-get update && \ | |
| adduser pwuser | ||
|
|
||
| # === BAKE BROWSERS INTO IMAGE === | ||
| # Browsers are split into separate layers to enable parallel pulls. | ||
|
|
||
| ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright | ||
|
|
||
| # 1. Add tip-of-tree Playwright package to install its browsers. | ||
| # The package should be built beforehand from tip-of-tree Playwright. | ||
| COPY ./playwright-core.tar.gz /tmp/playwright-core.tar.gz | ||
|
|
||
| # 2. Bake in browsers & deps. | ||
| # Browsers will be downloaded in `/ms-playwright`. | ||
| # Note: make sure to set 777 to the registry so that any user can access | ||
| # registry. | ||
| # 2. Set up playwright-core and install all system dependencies in one layer. | ||
| RUN mkdir /ms-playwright && \ | ||
| mkdir /ms-playwright-agent && \ | ||
| cd /ms-playwright-agent && npm init -y && \ | ||
| npm i /tmp/playwright-core.tar.gz && \ | ||
| npm exec --no -- playwright-core mark-docker-image "${DOCKER_IMAGE_NAME_TEMPLATE}" && \ | ||
| npm exec --no -- playwright-core install --with-deps && rm -rf /var/lib/apt/lists/* && \ | ||
| npm exec --no -- playwright-core install-deps chromium chromium-headless-shell firefox webkit && \ | ||
| rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # 3. Install each browser binary in its own layer for parallel pulling. | ||
| RUN cd /ms-playwright-agent && \ | ||
| npm exec --no -- playwright-core install chromium chromium-headless-shell && \ | ||
| chmod -R 777 /ms-playwright/chromium-* /ms-playwright/chromium_headless_shell-* | ||
|
|
||
| RUN cd /ms-playwright-agent && \ | ||
| npm exec --no -- playwright-core install firefox && \ | ||
| chmod -R 777 /ms-playwright/firefox-* | ||
|
|
||
| RUN cd /ms-playwright-agent && \ | ||
| npm exec --no -- playwright-core install webkit && \ | ||
| chmod -R 777 /ms-playwright/webkit-* | ||
|
|
||
| # 4. Install ffmpeg and clean up. | ||
| RUN cd /ms-playwright-agent && \ | ||
| npm exec --no -- playwright-core install ffmpeg && \ | ||
| rm /tmp/playwright-core.tar.gz && \ | ||
| rm -rf /ms-playwright-agent && \ | ||
| rm -rf ~/.npm/ && \ | ||
| chmod -R 777 /ms-playwright | ||
| chmod -R 777 /ms-playwright/ffmpeg-* | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,9 +36,8 @@ tag_and_push() { | |
| local source="$1" | ||
| local target="$2" | ||
| echo "-- tagging: $target" | ||
| docker tag $source $target | ||
| docker push $target | ||
| attach_eol_manifest $target | ||
| docker buildx imagetools create --tag "$target" "$source" | ||
| attach_eol_manifest "$target" | ||
| } | ||
|
|
||
| attach_eol_manifest() { | ||
|
|
@@ -79,11 +78,24 @@ publish_docker_images_with_arch_suffix() { | |
| fi | ||
| # Prune docker images to avoid platform conflicts | ||
| docker system prune -fa | ||
| ./build.sh "--${ARCH}" "${FLAVOR}" playwright:localbuild | ||
|
|
||
| for ((i = 0; i < ${#TAGS[@]}; i++)) do | ||
| local CANONICAL_TAG="playwright.azurecr.io/public/${MCR_IMAGE_NAME}:${TAGS[0]}-${ARCH}" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. revert |
||
|
|
||
| # Build and push the canonical tag. | ||
| node ../../utils/pack_package.js playwright-core ./playwright-core.tar.gz | ||
| docker buildx build \ | ||
| --platform "linux/${ARCH}" \ | ||
| --push \ | ||
| -f "Dockerfile.${FLAVOR}" \ | ||
| -t "${CANONICAL_TAG}" \ | ||
| . | ||
| rm -f playwright-core.tar.gz | ||
| attach_eol_manifest "${CANONICAL_TAG}" | ||
|
|
||
| # Create additional tags via registry-side copy (no re-upload). | ||
| for ((i = 1; i < ${#TAGS[@]}; i++)) do | ||
| local TAG="${TAGS[$i]}" | ||
| tag_and_push playwright:localbuild "playwright.azurecr.io/public/${MCR_IMAGE_NAME}:${TAG}-${ARCH}" | ||
| tag_and_push "${CANONICAL_TAG}" "playwright.azurecr.io/public/${MCR_IMAGE_NAME}:${TAG}-${ARCH}" | ||
| done | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert