Skip to content

Natively support gRPC on the docker socket#50744

Merged
akerouanton merged 1 commit intomoby:masterfrom
dmcgowan:add-grpc-support
Dec 12, 2025
Merged

Natively support gRPC on the docker socket#50744
akerouanton merged 1 commit intomoby:masterfrom
dmcgowan:add-grpc-support

Conversation

@dmcgowan
Copy link
Member

@dmcgowan dmcgowan commented Aug 15, 2025

Adds support for directly serving the gRPC server on the Docker socket. The requests can be easily detected from the content-type and use of HTTP 2. Explicitly adds the HTTP 2 protocol when using tcp+tls or unencrypted HTTP 2 when using a unix socket.

Only registers the buildkit service, which will allow deprecating the hijacking /session and /grpc endpoints. In the future, we can register more services using containerd style plugins.

- Human readable description for the release notes

Natively support gRPC on the listening socket

Comment on lines -38 to -43
client.WithSessionDialer(func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
return c.DialHijack(ctx, "/session", proto, meta)
}),
client.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
return c.DialHijack(ctx, "/grpc", "h2c", nil)
}),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to keep the /grpc and /session endpoints? IIUC removing these means that any existing version of buildx or the CLI will break?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, ignore me; this is client-side 🙈

I guess clients should still try both to stay compatible, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker/buildx#3369 handles both, I don't know if any other clients are using and if we can just safely deprecate those endpoints since using them requires a very delicate dance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically any version of buildx that currently exists; i.e., we can't remove the old endpoints, as they're part of the API

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use HTTP 301 Moved Permanently for legacy clients to switch to the updated endpoint?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW; this is only the test, not the endpoint being removed (🙈); so probably fine, but we can consider officially deprecating the old endpoints

@thaJeztah
Copy link
Member

This failure looks potentially related;

=== RUN   TestDockerDaemonSuite/TestTLSVerify
    docker_cli_daemon_test.go:708: Daemon should not have started due to missing certs: exit status 2
        panic: runtime error: invalid memory address or nil pointer dereference
        [signal SIGSEGV: segmentation violation code=0x1 addr=0x70 pc=0x55aaeba7cbfa]

Details
=== RUN   TestDockerDaemonSuite/TestTLSVerify
    docker_cli_daemon_test.go:708: Daemon should not have started due to missing certs: exit status 2
        panic: runtime error: invalid memory address or nil pointer dereference
        [signal SIGSEGV: segmentation violation code=0x1 addr=0x70 pc=0x55aaeba7cbfa]
        
        goroutine 1 [running, locked to thread]:
        github.com/moby/moby/v2/daemon/command.newAPIServerTLSConfig(0xc0001db700?)
        	/go/src/github.com/docker/docker/daemon/command/daemon.go:822 +0x15a
        github.com/moby/moby/v2/daemon/command.newDaemonCLI(0xc0001db700)
        	/go/src/github.com/docker/docker/daemon/command/daemon.go:90 +0x33
        github.com/moby/moby/v2/daemon/command.newDaemonCommand.func1(0xc000576008, {0xc0002d2940?, 0x7?, 0x55aaebafb48e?})
        	/go/src/github.com/docker/docker/daemon/command/docker.go:35 +0x65
        github.com/spf13/cobra.(*Command).execute(0xc000576008, {0xc0000500f0, 0x1, 0x1})
        	/go/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:1015 +0xaaa
        github.com/spf13/cobra.(*Command).ExecuteC(0xc000576008)
        	/go/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:1148 +0x46f
        github.com/spf13/cobra.(*Command).Execute(...)
        	/go/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:1071
        github.com/spf13/cobra.(*Command).ExecuteContext(...)
        	/go/src/github.com/docker/docker/vendor/github.com/spf13/cobra/command.go:1064
        github.com/moby/moby/v2/daemon/command.daemonRunner.Run({0x55aaec77d9d8?}, {0x55aaec7acfd0, 0x55aaede48200})
        	/go/src/github.com/docker/docker/daemon/command/docker.go:111 +0x6e
        main.main()
        	/go/src/github.com/docker/docker/cmd/dockerd/main.go:38 +0x126
        
        goroutine 8 [select]:
        go.opencensus.io/stats/view.(*worker).start(0xc0004dab00)
        	/go/src/github.com/docker/docker/vendor/go.opencensus.io/stats/view/worker.go:292 +0x9f
        created by go.opencensus.io/stats/view.init.0 in goroutine 1
        	/go/src/github.com/docker/docker/vendor/go.opencensus.io/stats/view/worker.go:34 +0x8d
        
        goroutine 43 [select]:
        io.(*pipe).read(0xc000113980, {0xc000610000, 0x10000, 0x0?})
        	/usr/local/go/src/io/pipe.go:57 +0xa5
        io.(*PipeReader).Read(0xc0004d9e08?, {0xc000610000?, 0x55aaede4b880?, 0xc0004d9e60?})
        	/usr/local/go/src/io/pipe.go:134 +0x1a
        bufio.(*Scanner).Scan(0xc0004d9f28)
        	/usr/local/go/src/bufio/scan.go:219 +0x81e
        github.com/sirupsen/logrus.(*Entry).writerScanner(0xc0001acf50, 0xc000113980, 0xc0002d2550)
        	/go/src/github.com/docker/docker/vendor/github.com/sirupsen/logrus/writer.go:86 +0x11d
        created by github.com/sirupsen/logrus.(*Entry).WriterLevel in goroutine 1
        	/go/src/github.com/docker/docker/vendor/github.com/sirupsen/logrus/writer.go:57 +0x31f
        
        goroutine 42 [select]:
        io.(*pipe).read(0xc000113920, {0xc00058c000, 0x10000, 0x0?})
        	/usr/local/go/src/io/pipe.go:57 +0xa5
        io.(*PipeReader).Read(0xc0004d8e08?, {0xc00058c000?, 0xc0005cc000?, 0xc0004d8e60?})
        	/usr/local/go/src/io/pipe.go:134 +0x1a
        bufio.(*Scanner).Scan(0xc0004d8f28)
        	/usr/local/go/src/bufio/scan.go:219 +0x81e
        github.com/sirupsen/logrus.(*Entry).writerScanner(0xc0001acf50, 0xc000113920, 0xc0002d2540)
        	/go/src/github.com/docker/docker/vendor/github.com/sirupsen/logrus/writer.go:86 +0x11d
        created by github.com/sirupsen/logrus.(*Entry).WriterLevel in goroutine 1
        	/go/src/github.com/docker/docker/vendor/github.com/sirupsen/logrus/writer.go:57 +0x31f
        
        goroutine 44 [select]:
        io.(*pipe).read(0xc0001139e0, {0xc000680000, 0x10000, 0x0?})
        	/usr/local/go/src/io/pipe.go:57 +0xa5
        io.(*PipeReader).Read(0xc0004d4608?, {0xc000680000?, 0x55aaede4b880?, 0xc0004d4660?})
        	/usr/local/go/src/io/pipe.go:134 +0x1a
        bufio.(*Scanner).Scan(0xc000094f28)
        	/usr/local/go/src/bufio/scan.go:219 +0x81e
        github.com/sirupsen/logrus.(*Entry).writerScanner(0xc0001acf50, 0xc0001139e0, 0xc0002d2570)
        	/go/src/github.com/docker/docker/vendor/github.com/sirupsen/logrus/writer.go:86 +0x11d
        created by github.com/sirupsen/logrus.(*Entry).WriterLevel in goroutine 1
        	/go/src/github.com/docker/docker/vendor/github.com/sirupsen/logrus/writer.go:57 +0x31f

@thaJeztah
Copy link
Member

Looks like this requires updating the github.com/moby/moby/v2 module to use go1.24 as minimum;

INFO [runner] linters took 2m14.274822457s with stages: goanalysis_metalinter: 2m14.21043666s 
daemon/command/daemon.go:313:14: stdversion: http.Protocols requires go1.24 or later (file is go1.23) (govet)
		httpServer.Protocols = &p
		           ^
daemon/command/daemon.go:318:14: stdversion: http.Protocols requires go1.24 or later (file is go1.23) (govet)
		httpServer.Protocols = &p
		           ^
daemon/command/httphandler.go:73:84: stdversion: http.Protocols requires go1.24 or later (file is go1.23) (govet)
func supportGRPC(ctx context.Context, lss []net.Listener, hasTLS bool) (bool, http.Protocols) {
                                                                                   ^
daemon/command/httphandler.go:74:13: stdversion: http.Protocols requires go1.24 or later (file is go1.23) (govet)
	var p http.Protocols
	           ^

From #49836 (comment), I saw that @corhere originally suggested some approach through golang.org/x/net, but not sure if that would allow working without go1.24;

It would be trivial for us to support HTTP/2 with Prior Knowledge – and also properly support the deprecated Upgrade: h2c mechanism in a way that actually complies with RFC 7540 Section 3.2 by wrapping the daemon's HTTP handler in https://pkg.go.dev/golang.org/x/[email protected]/http2/h2c

Also;

#49836 (comment)

I suspected the Upgrade connected more directly with a grpc-go server, and that's why it needed to be POST specifically to /grpc

It's more straightforward than that. We implemented the h2c upgrade incorrectly and added it as a route to the HTTP server mux instead of wrapping the HTTP server mux like we should have.

@thaJeztah
Copy link
Member

I pushed a commit to update the Go version in go.mod so that CI can run with that change.

@thaJeztah thaJeztah added area/api API impact/api kind/enhancement Enhancements are not bugs or new features but can improve usability or performance. labels Aug 15, 2025
@corhere
Copy link
Contributor

corhere commented Aug 15, 2025

100% using golang.org/x/net like I suggested would not require bumping the go version. And I would still prefer that approach over open-coding it again since we got it wrong open-coding it the first time around.

@dmcgowan
Copy link
Member Author

100% using golang.org/x/net like I suggested would not require bumping the go version.

1.23 is deprecated, the min version will get bumped as soon as we vendor in a project which bumps it (containerd 2.2 or anything that has a transitive Kubernetes dep)

And I would still prefer that approach over open-coding it again since we got it wrong open-coding it the first time around.

Can you explain this one further. I usually think the preferred solution is using the standard library as the Go maintainers have encouraged not using those lower level libraries. "This package is low-level and intended to be used directly by very few people", we aren't doing anything that special here. Not sure what "over open-coding it again" refers to or how it relates to the existing hijack approach, which I think we all agree isn't great.

Use the GRPC server when requests are for the grpc content type.

Signed-off-by: Derek McGowan <[email protected]>
@akerouanton
Copy link
Member

I’ve rebased this PR and removed the supportGRPC function, which previously determined whether gRPC and h2c were enabled. h2c is now always enabled.

When a TLS listener receives a connection, it returns an error if the handshake can’t be completed. As a result, h2c won’t be used for TLS listeners, but it will be used for non-TLS TCP connections and the UNIX socket.

In the previous iteration, gRPC was disabled when running the dev container with the API exposed over TCP. This is now resolved.

@akerouanton akerouanton marked this pull request as ready for review December 11, 2025 08:57
@akerouanton akerouanton modified the milestones: v-future, 29.2.0 Dec 11, 2025
Copy link
Contributor

@vvoland vvoland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

})
httpServer.Handler = apiServer.CreateMux(ctx, routers...)
gs := newGRPCServer(ctx)
b.backend.RegisterGRPC(gs)
Copy link
Contributor

@vvoland vvoland Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This will implicitly expose the containerd content store via buildkit:

store := &roContentStore{c.opt.ContentStore.WithFallbackNS(c.opt.ContentStore.Namespace() + "_history")}

It's read-only so that's good, but we should make that explicitly registered by the Moby and not implicitly by Buildkit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot can you open a ticket for this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for nothing @copilot! I did it myself: #51698

@vvoland vvoland moved this from Complete to New in 🔦 Maintainer spotlight Dec 11, 2025
Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@akerouanton akerouanton merged commit 3cba626 into moby:master Dec 12, 2025
259 of 260 checks passed
@vvoland vvoland added kind/feature Functionality or other elements that the project doesn't currently have. Features are new and shiny and removed kind/enhancement Enhancements are not bugs or new features but can improve usability or performance. labels Dec 16, 2025
@thaJeztah thaJeztah moved this from New to Complete in 🔦 Maintainer spotlight Dec 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api API area/daemon Core Engine impact/api impact/changelog kind/feature Functionality or other elements that the project doesn't currently have. Features are new and shiny

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Proposal: c8d: expose contentstore API

8 participants