kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Docker Desktop の Extensions を実装してみよう

2023年1月にリリースされた Docker Desktop v4.16.0 (release notes) で GA になった「Docker Extensions」の Extensions は段々と種類が増えてきて,Disk Usage / Aqua Trivy / LocalStack など,最近使う機会もあったりする.今回は "Quickstart" ドキュメントを読みながら Docker Extensions の実装に入門してみた❗️

\( 'ω')/ オレオレ Docker Extension を実装するぞー

docs.docker.com

🐳 docker extension コマンド

まず,Docker Extensions を実装するためには docker extension コマンドを使う.コマンド一覧を以下に載せておく.

$ docker extension --help

Usage:  docker extension [OPTIONS] COMMAND

Manages Docker extensions

Options:
      --socket string   The Desktop extension manager socket

Management Commands:
  dev             Extension development helpers

Commands:
  init            Create a new Docker Extension based on a template.
  install         Install a Docker extension with the specified image
  ls              List installed Docker extensions
  rm              Remove a Docker extension
  share           Generate a link to share the extension.
  update          Remove and re-install a Docker extension
  validate        Validate an extension image or metadata file
  version         Print the client and server versions

Run 'docker extension COMMAND --help' for more information on a command.

$ docker extension dev --help

Usage:  docker extension dev COMMAND

Extension development helpers

Commands:
  debug       Set the debug mode for an extension
  reset       Reset the source and disables the debug mode for the extension UI
  ui-source   Set a new source for the extension UI

Run 'docker extension dev COMMAND --help' for more information on a command.

🐳 検証環境

今回は Docker Desktop for Mac v4.16.2 と,以下の Docker Extensions を検証環境として使う.

$ docker extension version
Client Version: v0.2.17
Server API Version: 0.3.3

🐳 docker extension init コマンド

まず docker extension init コマンドを実行すると Docker Extensions のプロジェクトを簡単にセットアップできる.Quickstart に沿って,今回は my-extension という名前にする.初期化中のログを見ると Go backendReact app など,馴染みのある技術スタックが使われていることがわかる❗️

$ docker extension init my-extension
? Title: my-extension
? Description: my-extension
? Vendor: kakakakakku
? Image Repository where the extension will be pushed: kakakakakku/my-extension

(中略)

Creating a Go backend service...
Initializing new go module...
Creating a React app...
Copying ui dir...
Renaming some files...
Installing npm packages, this may take a few minutes...

(中略)

プロジェクトディレクトリは以下のようになる(tree コマンドで2階層目まで表示している).

$ tree -L 2 .
.
├── Dockerfile
├── Makefile
├── README.md
├── backend
│   ├── go.mod
│   ├── go.sum
│   └── main.go
├── docker-compose.yaml
├── docker.svg
├── metadata.json
└── ui
    ├── index.html
    ├── node_modules
    ├── package-lock.json
    ├── package.json
    ├── public
    ├── src
    ├── tsconfig.json
    ├── tsconfig.node.json
    └── vite.config.ts

6 directories, 15 files

Docker Extensions を実装する前に「構成」を理解しておく必要がある.Docker Extensions は,重要な2つのコンポーネント「UI (例: React x TypeScript x MUI)」「Backend (例: Go)」を組み合わせて実装する❗️以下に UI (Frontend) と Backend の関係性がまとまった図をドキュメントから引用して載せておく.

Extension architecture | Docker Documentation より引用

ちなみに「UI」は React に限定されているわけではなく Vue / Anguler なども使える.「Backend」も Go に限定されているわけではなく Node.js / Python なども使える.よって,Docker Extensions を実装する技術スタックとしては「自由度がある」とも言える.詳しくはドキュメント参照〜

docs.docker.com

さらに React コンポーネントとしてデフォルトで導入されている「MUI(旧 Material UI)」も限定されているわけではなく,他のライブラリを使うこともできる.しかし,Docker Extensions のドキュメントには「React x MUI 推奨」と書いてある.Docker Extensions デザインの一貫性 (look & feel) の観点や今後 MUI を拡張する予定もあるらしく,基本的には MUI を使うのが良さそう.

mui.com

docs.docker.com

🐳 Dockerfile

次に Dockerfile を確認する.初期化したときに作られた Dockerfile は以下のようになっていた.ポイントは Multi-stage builds を使って Backend (Go) と UI (React) をビルドして,最終的に Alpine イメージにまとめているところ.

FROM golang:1.19-alpine AS builder
ENV CGO_ENABLED=0
WORKDIR /backend
COPY backend/go.* .
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go mod download
COPY backend/. .
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -trimpath -ldflags="-s -w" -o bin/service

FROM --platform=$BUILDPLATFORM node:18.12-alpine3.16 AS client-builder
WORKDIR /ui
# cache packages in layer
COPY ui/package.json /ui/package.json
COPY ui/package-lock.json /ui/package-lock.json
RUN --mount=type=cache,target=/usr/src/app/.npm \
    npm set cache /usr/src/app/.npm && \
    npm ci
# install
COPY ui /ui
RUN npm run build

FROM alpine
LABEL org.opencontainers.image.title="my-extension" \
    org.opencontainers.image.description="my-extension" \
    org.opencontainers.image.vendor="kakakakakku" \
    com.docker.desktop.extension.api.version="0.3.3" \
    com.docker.extension.screenshots="" \
    com.docker.extension.detailed-description="" \
    com.docker.extension.publisher-url="" \
    com.docker.extension.additional-urls="" \
    com.docker.extension.changelog=""

COPY --from=builder /backend/bin/service /
COPY docker-compose.yaml .
COPY metadata.json .
COPY docker.svg .
COPY --from=client-builder /ui/build ui
CMD /service -socket /run/guest-services/backend.sock

🐳 コンテナイメージをビルドする

手順に戻る!今度は docker build コマンドを実行して Dockerfile からコンテナイメージをビルドする.Multi-stage builds を使っているので,最終的に 12.6MB とサイズも小さく抑えられている.

$ docker build -t kakakakakku/my-extension .

$ docker image ls kakakakakku/my-extension
REPOSITORY                 TAG       IMAGE ID       CREATED              SIZE
kakakakakku/my-extension   latest    ef80c4794f8b   About a minute ago   12.6MB

🐳 docker extension install コマンド

最後は docker extension install コマンドを実行して Docker Extensions を Docker Desktop にインストールすれば完了〜

$ docker extension install kakakakakku/my-extension
Extensions can install binaries, invoke commands and access files on your machine.
Are you sure you want to continue? [y/N] y
Installing new extension "kakakakakku/my-extension"
Installing service in Desktop VM...
Setting additional compose attributes
VM service started
Installing Desktop extension UI for tab "My-Extension"...
Extension UI tab "My-Extension" added.
Extension "my-extension" installed successfully

Docker Desktop に my-extension が表示されたぁぁぁぁぁ❗️

🐳 docker extension update コマンド

次に UI (React) と Backend (Go) それぞれのコードを修正して,Docker Extensions に更新を反映する.もう一度 docker build コマンドを実行して,今度は docker extension update コマンドを実行する.すると Docker Extensions に更新が反映されたぁぁぁぁぁ❗️

$ docker build -t kakakakakku/my-extension .
$ docker extension update kakakakakku/my-extension

🐳 ホットリロード

特にフロントエンド側の開発は試行錯誤を伴うため,毎回ビルドせずにコードを修正したらすぐに「ホットリロード」する仕組みがある(正確には ViteHot Module Replacement (HMR) を使っている).手順としては npm run dev コマンドを実行して http://localhost:3000 に UI アプリケーションを起動し,docker extension dev ui-source コマンドを実行して UI コンポーネントの参照先を localhost にする.あとはコードを修正するとほぼリアルタイムに Docker Desktop に反映されるため,開発体験が良くなる❗️詳しくはドキュメント参照〜

$ cd ui
$ npm run dev
$ docker extension dev ui-source kakakakakku/my-extension http://localhost:3000
UI source for the extension "kakakakakku/my-extension" changed to "http://localhost:3000"%

docs.docker.com

🐳 アイコンを変更する

Docker Desktop の Extensions 一覧に表示されるアイコンは初期化したプロジェクトに含まれている docker.svg で,metadata.json で設定する仕組みになっている.

{
  "icon": "docker.svg",
  "vm": {
    "composefile": "docker-compose.yaml",
    "exposes": {
      "socket": "backend.sock"
    }
  },
  "ui": {
    "dashboard-tab": {
      "title": "My-Extension",
      "src": "index.html",
      "root": "ui",
      "backend": {
        "socket": "backend.sock"
      }
    }
  }
}

さらに docker.svgDockerfileCOPY docker.svg . を実行してコンテナイメージに含まれている.metadata.jsonDockerfile を修正すると Docker Extensions のアイコンを変更できる.

ちなみに Dockerfile に記述できる LABELcom.docker.desktop.extension.icon もあって混乱するけど,LABEL は Docker Extensions を Extensions Marketplace に公開するときに使われる.二重に設定してるように感じてややこしく感じたけど,仕様である旨はドキュメントに書いてあった.

In the example Dockerfile, you can see that the image label com.docker.desktop.extension.icon is set to an icon URL. The Extensions Marketplace displays this icon without installing the extension. The Dockerfile also includes COPY docker.svg . to copy an icon file inside the image. This second icon file is used to display the extension UI in the Dashboard, once the extension is installed.
Create an advanced frontend extension | Docker Documentation

docs.docker.com

🐳 Extensions Marketplace に公開する

Docker Extensions の実装と動作確認が終わったら Extensions Marketplace に公開できる.今回は "Quickstart" を試しただけなので割愛する.公開するプロセスは以下のドキュメントに詳しく載っている.

docs.docker.com

🐳 Makefile

今回は docker extension コマンドを実行したけど,初期化したプロジェクトには Makefile も含まれている.よって,make build-extensionmake install-extension など,簡単に実行することもできる❗️

$ make help
Please specify a build target. The choices are:
build-extension                Build service image to be deployed as a desktop extension
install-extension              Install the extension
update-extension               Update the extension
prepare-buildx                 Create buildx builder for multi-arch build, if not exists
push-extension                 Build & Upload extension image to hub. Do not push if tag already exists: make push-extension tag=0.1
help                           Show this help

🐳 まとめ

今回は Docker Extensions の "Quickstart" ドキュメントを読みながら,Hello World レベルではあるけど「オレオレ Docker Extension」を実装してみた❗️実装の流れを把握できて良かった.次はもっと実用的な Docker Extension を実装してみたいぞ〜