Skip to content
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

Allow user to separate http and grpc server #15446

Merged
merged 5 commits into from
Mar 30, 2023

Conversation

serathius
Copy link
Member

@serathius serathius commented Mar 10, 2023

Phase 2 of #15402 (comment)
Adding a flag to separate http server. No changes in default so the PR can be backported.

Difference in load configuration for watch delay tests show how huge the
impact is. Even with random write scheduler grpc under http
server can only handle 500 KB with 2 seconds delay. On the other hand,
separate grpc server easily hits 10, 100 or even 1000 MB within 100 miliseconds.

Priority write scheduler that was used in most previous releases
is far worse than random one.

Tests configured to only 10 MB to avoid them taking too long to fill
etcd.

cc @ahrtr @ptabor @spzala @fuweid @aojea

@serathius serathius marked this pull request as draft March 10, 2023 16:41
@serathius
Copy link
Member Author

Making as draft as I still need to implement more tests

@ahrtr
Copy link
Member

ahrtr commented Mar 12, 2023

A quick comment: suggest to extract the first commit into a separate PR, so that we can approve & merge it firstly.

@ahrtr
Copy link
Member

ahrtr commented Mar 13, 2023

I believe adding a new user facing flag "--listen-client-http-urls" can resolve #15402. But it also changes user behavior. All users have adapted the behavior of 2379 for all clients connection/request, and 2380 for peer requests. Now users have to add add a new port (e.g. 2381) for all http client requests (e.g. /health, /metrics, etc.). I think it's a non-trivial change, and we should get more feedback before we start to make change.

From users perspective, I think it still makes sense to multiplex both gRPC and HTTP (including 1 and 2) using one port (2379), and personally I think it should still be the long-term goal.

gRPC-go has always been a pain point in etcd, which is still depending on some experimental APIs right now (see #15145), one of which is grpcServer.ServeHTTP.

I think

  • it would be great if this issue can be resolved by network layer instead of etcd?
  • or we follow this PR for now and eventually revert to the behavior of multiplexing gRPC and HTTP in one port for all client connection/requests in the future when network layer resolves such issue?
  • or follow the solution similar to etcdserver: create separate goroutine to accept grpc tls connection #15451?

We (most etcd contributors) are not network (e.g. gRPC, golang.org/x/net, etc) experts, so it would be great if we can get more feedback from network experts. cc @dfawley @aojea

@ahrtr
Copy link
Member

ahrtr commented Mar 13, 2023

cc K8s folks @liggitt @dims @nikhita @neolit123 @wojtek-t

@serathius
Copy link
Member Author

Please let's move discussion to the original issue #15402 where this design was approved by @ptab, @mborsz and @aojea.

The change is non-trivial, but everyone I talked to agreed that this is the correct long-term direction. GRPC and http should not be mixed, @aojea even called it "an abomination".

@aojea
Copy link

aojea commented Mar 13, 2023

GRPC and http should not be mixed, @aojea even called it "an abomination".

well, I just was trying to add some drama 😄 , but you can see similar comments from its own author grpc/grpc-go#586 (comment) in this issue that comments about the performance regression of using ServeHTTP

And I don't officially work on grpc-go. It's not surprising that ServeHTTP is slower considering it's shoehorned into the existing ServerTransport interface somewhat awkwardly.

and on the documentation it is still considered Experimental

https://pkg.go.dev/google.golang.org/grpc#Server.ServeHTTP

@serathius serathius changed the title Separate grpc server Allow user to separate http and grpc server Mar 13, 2023
@ptabor
Copy link
Contributor

ptabor commented Mar 13, 2023

@ahrtr - I agree that all changes requiring action from customers are difficult and should go through a deep consideration.

I think that this PR is not yet controversial and not introducing the breaking change. It's just adding new
(safe) mode of operation.
The breaking change (changing default) will be a follow up PR... that will not be backported. Users will have both v3.4 and v3.5 to gracefully migrated to the new safer semantic and only in v3.6 (or later) we will change the default.
Etcd minor releases are so in frequent that we do use it as a train to ship breaking changes when they are justified by project's robustness or security.

@serathius serathius force-pushed the separate-grpc-server branch 8 times, most recently from 15f13fb to dde34f2 Compare March 16, 2023 16:43
@serathius
Copy link
Member Author

serathius commented Mar 17, 2023

Hmm cmux test failure ?!

2023-03-16T16:56:35.0274754Z --- FAIL: TestConnectionMultiplexing (13.68s)
2023-03-16T16:56:35.0275287Z     before.go:36: Changing working directory to: /tmp/TestConnectionMultiplexing1002003079/001
2023-03-16T16:56:35.0275898Z     --- FAIL: TestConnectionMultiplexing/ServerTLSAndNonTLS (11.68s)
2023-03-16T16:56:35.0276887Z         logger.go:130: 2023-03-16T16:46:10.486Z	INFO	starting server...	{"name": "TestConnectionMultiplexingServerTLSAndNonTLS-test-0"}
2023-03-16T16:56:35.0284372Z         logger.go:130: 2023-03-16T16:46:10.487Z	INFO	spawning process	{"args": ["/home/runner/work/etcd/etcd/bin/etcd", "--name", "TestConnectionMultiplexingServerTLSAndNonTLS-test-0", "--listen-client-urls", "http://localhost:20000,https://localhost:20000", "--advertise-client-urls", "http://localhost:20000,https://localhost:20000", "--listen-peer-urls", "http://localhost:20001", "--initial-advertise-peer-urls", "http://localhost:20001", "--initial-cluster-token", "", "--data-dir", "/tmp/TestConnectionMultiplexingServerTLSAndNonTLS4257936873/001", "--snapshot-count", "10000", "--strict-reconfig-check=false", "--cert-file", "/home/runner/work/etcd/etcd/tests/fixtures/server.crt", "--key-file", "/home/runner/work/etcd/etcd/tests/fixtures/server.key.insecure", "--trusted-ca-file", "/home/runner/work/etcd/etcd/tests/fixtures/ca.crt", "--initial-cluster", "TestConnectionMultiplexingServerTLSAndNonTLS-test-0=http://localhost:20001", "--initial-cluster-state", "new"], "working-dir": "/tmp/TestConnectionMultiplexing1002003079/001", "name": "TestConnectionMultiplexingServerTLSAndNonTLS-test-0", "environment-variables": ["ETCD_VERIFY=all", "EXPECT_DEBUG=true", "PATH=/opt/hostedtoolcache/go/1.19.7/x64/bin:/home/runner/go/bin:/opt/hostedtoolcache/go/1.19.7/x64/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin", "ETCD_UNSUPPORTED_ARCH=386"]}
2023-03-16T16:56:35.0287532Z         logger.go:130: 2023-03-16T16:46:11.214Z	INFO	started server.	{"name": "TestConnectionMultiplexingServerTLSAndNonTLS-test-0", "pid": 10906}
2023-03-16T16:56:35.0288314Z         --- FAIL: TestConnectionMultiplexing/ServerTLSAndNonTLS/ClientNonTLS (10.24s)
2023-03-16T16:56:35.0289249Z             --- FAIL: TestConnectionMultiplexing/ServerTLSAndNonTLS/ClientNonTLS/etcdctl (5.02s)
2023-03-16T16:56:35.0289801Z                 cmux_test.go:101: 
2023-03-16T16:56:35.0290604Z                     	Error Trace:	/home/runner/work/etcd/etcd/tests/e2e/cmux_test.go:101
2023-03-16T16:56:35.0291609Z                     	Error:      	Received unexpected error:
2023-03-16T16:56:35.0294285Z                     	            	match not found.  Set EXPECT_DEBUG for more info Errs: [unexpected exit code [1] after running [/home/runner/work/etcd/etcd/bin/etcdctl --endpoints=http://localhost:20000 get a -w json -w json -w json]], last lines:
2023-03-16T16:56:35.0300027Z                     	            	{"level":"warn","ts":"2023-03-16T16:46:16.930432Z","logger":"etcd-client","caller":"[email protected]/retry_interceptor.go:65","msg":"retrying of unary invoker failed","target":"etcd-endpoints://0xaa00700/localhost:20000","method":"/etcdserverpb.KV/Range","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = latest balancer error: last connection error: connection error: desc = \"error reading server preface: read tcp 127.0.0.1:39924->127.0.0.1:20000: read: connection reset by peer\""}
2023-03-16T16:56:35.0301247Z                     	            	Error: context deadline exceeded
2023-03-16T16:56:35.0302107Z                     	Test:       	TestConnectionMultiplexing/ServerTLSAndNonTLS/ClientNonTLS/etcdctl
2023-03-16T16:56:35.0302942Z             --- FAIL: TestConnectionMultiplexing/ServerTLSAndNonTLS/ClientNonTLS/clientv3 (5.00s)
2023-03-16T16:56:35.0303569Z                 utils.go:53: context deadline exceeded

Can't repro locally.

@aojea
Copy link

aojea commented Mar 17, 2023

/cc

@fuweid
Copy link
Member

fuweid commented Mar 17, 2023

Hmm cmux test failure ?!


2023-03-16T16:56:35.0274754Z --- FAIL: TestConnectionMultiplexing (13.68s)

2023-03-16T16:56:35.0275287Z     before.go:36: Changing working directory to: /tmp/TestConnectionMultiplexing1002003079/001

2023-03-16T16:56:35.0275898Z     --- FAIL: TestConnectionMultiplexing/ServerTLSAndNonTLS (11.68s)

2023-03-16T16:56:35.0276887Z         logger.go:130: 2023-03-16T16:46:10.486Z	INFO	starting server...	{"name": "TestConnectionMultiplexingServerTLSAndNonTLS-test-0"}

2023-03-16T16:56:35.0284372Z         logger.go:130: 2023-03-16T16:46:10.487Z	INFO	spawning process	{"args": ["/home/runner/work/etcd/etcd/bin/etcd", "--name", "TestConnectionMultiplexingServerTLSAndNonTLS-test-0", "--listen-client-urls", "http://localhost:20000,https://localhost:20000", "--advertise-client-urls", "http://localhost:20000,https://localhost:20000", "--listen-peer-urls", "http://localhost:20001", "--initial-advertise-peer-urls", "http://localhost:20001", "--initial-cluster-token", "", "--data-dir", "/tmp/TestConnectionMultiplexingServerTLSAndNonTLS4257936873/001", "--snapshot-count", "10000", "--strict-reconfig-check=false", "--cert-file", "/home/runner/work/etcd/etcd/tests/fixtures/server.crt", "--key-file", "/home/runner/work/etcd/etcd/tests/fixtures/server.key.insecure", "--trusted-ca-file", "/home/runner/work/etcd/etcd/tests/fixtures/ca.crt", "--initial-cluster", "TestConnectionMultiplexingServerTLSAndNonTLS-test-0=http://localhost:20001", "--initial-cluster-state", "new"], "working-dir": "/tmp/TestConnectionMultiplexing1002003079/001", "name": "TestConnectionMultiplexingServerTLSAndNonTLS-test-0", "environment-variables": ["ETCD_VERIFY=all", "EXPECT_DEBUG=true", "PATH=/opt/hostedtoolcache/go/1.19.7/x64/bin:/home/runner/go/bin:/opt/hostedtoolcache/go/1.19.7/x64/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin", "ETCD_UNSUPPORTED_ARCH=386"]}

2023-03-16T16:56:35.0287532Z         logger.go:130: 2023-03-16T16:46:11.214Z	INFO	started server.	{"name": "TestConnectionMultiplexingServerTLSAndNonTLS-test-0", "pid": 10906}

2023-03-16T16:56:35.0288314Z         --- FAIL: TestConnectionMultiplexing/ServerTLSAndNonTLS/ClientNonTLS (10.24s)

2023-03-16T16:56:35.0289249Z             --- FAIL: TestConnectionMultiplexing/ServerTLSAndNonTLS/ClientNonTLS/etcdctl (5.02s)

2023-03-16T16:56:35.0289801Z                 cmux_test.go:101: 

2023-03-16T16:56:35.0290604Z                     	Error Trace:	/home/runner/work/etcd/etcd/tests/e2e/cmux_test.go:101

2023-03-16T16:56:35.0291609Z                     	Error:      	Received unexpected error:

2023-03-16T16:56:35.0294285Z                     	            	match not found.  Set EXPECT_DEBUG for more info Errs: [unexpected exit code [1] after running [/home/runner/work/etcd/etcd/bin/etcdctl --endpoints=http://localhost:20000 get a -w json -w json -w json]], last lines:

2023-03-16T16:56:35.0300027Z                     	            	{"level":"warn","ts":"2023-03-16T16:46:16.930432Z","logger":"etcd-client","caller":"[email protected]/retry_interceptor.go:65","msg":"retrying of unary invoker failed","target":"etcd-endpoints://0xaa00700/localhost:20000","method":"/etcdserverpb.KV/Range","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = latest balancer error: last connection error: connection error: desc = \"error reading server preface: read tcp 127.0.0.1:39924->127.0.0.1:20000: read: connection reset by peer\""}

2023-03-16T16:56:35.0301247Z                     	            	Error: context deadline exceeded

2023-03-16T16:56:35.0302107Z                     	Test:       	TestConnectionMultiplexing/ServerTLSAndNonTLS/ClientNonTLS/etcdctl

2023-03-16T16:56:35.0302942Z             --- FAIL: TestConnectionMultiplexing/ServerTLSAndNonTLS/ClientNonTLS/clientv3 (5.00s)

2023-03-16T16:56:35.0303569Z                 utils.go:53: context deadline exceeded

Can't repro locally.

I think I run into this as well. https://github.com/etcd-io/etcd/actions/runs/4439049893/jobs/7790956875.

@serathius
Copy link
Member Author

@fuweid Thanks for confirming. Looks like a flake :(
Will file an issue to investigate it.

@fuweid
Copy link
Member

fuweid commented Mar 17, 2023

@fuweid Thanks for confirming. Looks like a flake :(

Will file an issue to investigate it.

If no one takes it, please assign it to me. Thanks~ 🤓

@serathius
Copy link
Member Author

@fuweid Thanks for confirming. Looks like a flake :(
Will file an issue to investigate it.

If no one takes it, please assign it to me. Thanks~ nerd_face

I just added the test :(, so let me take responsibility.

@serathius serathius force-pushed the separate-grpc-server branch 2 times, most recently from 258ceab to 39b2dfc Compare March 28, 2023 11:52
@serathius
Copy link
Member Author

Addressed all feedback, please take another look @ahrtr @ptabor

server/etcdmain/config.go Outdated Show resolved Hide resolved
server/embed/config.go Show resolved Hide resolved
}

sctx := sctxs[addr]
if sctx == nil {
Copy link
Member

Choose a reason for hiding this comment

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

It's strange but it is the status quo...

Comment on lines 681 to 682
// net.Listener will rewrite ipv4 0.0.0.0 to ipv6 [::], breaking
// hosts that disable ipv6. So, use the address given by the user.
Copy link
Member

Choose a reason for hiding this comment

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

Should the comment also be moved up to the appropriate location?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure what this comment refers to. I didn't move it.

Copy link
Member

Choose a reason for hiding this comment

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

It was introduced by #8221 to fix the grpc gateway creating grpc connections using [::] caused by sctx.listener.addr() when ipv6 is disabled but user configures 0.0.0.0 as listen client urls.

I think we can leave it as it is right now and improve it later.

tests/e2e/cmux_test.go Show resolved Hide resolved
tests/framework/e2e/cluster.go Outdated Show resolved Hide resolved
@ahrtr
Copy link
Member

ahrtr commented Mar 29, 2023

I think that this PR is not yet controversial

@ptabor
That's the concern, because of no counter-question. I still think it makes sense to expose only one port (e.g. 2379) to clients from users perspective. Users shouldn't care about gRPC or HTTP. I am not a network expert, and no extra bandwidth to have a deep dive either. But happy to see @aojea keeps trying #15510, I may spend some time on it in future.

Anyway, I agree this PR is the best choice for 3.4 and 3.5, and overall looks good. Great work! @serathius . (Please resolve the couple of minor comments from @chaochn47 )

@serathius
Copy link
Member Author

serathius commented Mar 29, 2023

I still think it makes sense to expose only one port (e.g. 2379) to clients from users perspective. Users shouldn't care about gRPC or HTTP.

Overall it's good for simplicity, however I don't think this should apply in this case. Aside http v2 and grpcgateway APIs that should be removed, all remaining http endpoints are administrative and users should not care about them.

Please think in terms different user personas, user that connects to etcd in their application and administrator that manages the etcd (healthchecks, upgrades, etc) it could be a human or automated operator. With this in mind it seems not only unnecessary but also wrong to mix client and administrative endpoints. As an administrator I would prefer to bind hide administrative endpoints from external traffic by binding them to local address.

I don't think this was discussed before as etcd API is usually already well protected with access requiring mtls. Still I think it's worth to consider starting an effort to clean up etcd API and have those personal in mind. If there is any API (maybe version) that is useful for users, it should have it's alternative in grpc. If there is an administrative API available to user that should require higher privilege, we should consider moving it to separate port.

Comment on lines 721 to 726
addr = u.Host
if u.Scheme == "unix" || u.Scheme == "unixs" {
addr = u.Host + u.Path
}
secure = u.Scheme == "https" || u.Scheme == "unixs"
network = "tcp"
if u.Scheme == "unix" || u.Scheme == "unixs" {
network = "unix"
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
addr = u.Host
if u.Scheme == "unix" || u.Scheme == "unixs" {
addr = u.Host + u.Path
}
secure = u.Scheme == "https" || u.Scheme == "unixs"
network = "tcp"
if u.Scheme == "unix" || u.Scheme == "unixs" {
network = "unix"
}
addr = u.Host
network = "tcp"
if u.Scheme == "unix" || u.Scheme == "unixs" {
addr = u.Host + u.Path
network = "unix"
}
secure = u.Scheme == "https" || u.Scheme == "unixs"

Copy link
Member

@ahrtr ahrtr left a comment

Choose a reason for hiding this comment

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

LGTM with a minor comment.

cc @ptabor @chaochn47 @fuweid to double check.

"List of URLs to listen on for client grpc traffic. Http services are available as long --listen-client-http-urls is not specified.",
)
fs.Var(
flags.NewUniqueURLsWithExceptions("", ""), "listen-client-http-urls",
Copy link
Member

@chaochn47 chaochn47 Mar 30, 2023

Choose a reason for hiding this comment

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

IMHO, there is a nuance that advertise-client-urls to include listen-client-http-urls after the split to allow communicating via http though just in case users rely on this behavior.

I think adding some warning message in the help string of the flag is enough for now.

…er separate from http server

Difference in load configuration for watch delay tests show how huge the
impact is. Even with random write scheduler grpc under http
server can only handle 500 KB with 2 seconds delay. On the other hand,
separate grpc server easily hits 10, 100 or even 1000 MB within 100 miliseconds.

Priority write scheduler that was used in most previous releases
is far worse than random one.

Tests configured to only 5 MB to avoid flakes and taking too long to fill
etcd.

Signed-off-by: Marek Siarkowicz <[email protected]>
Copy link
Contributor

@ptabor ptabor left a comment

Choose a reason for hiding this comment

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

LGTM. Thank you.

@serathius serathius merged commit 0bd0b6b into etcd-io:main Mar 30, 2023
Copy link
Member

@fuweid fuweid left a comment

Choose a reason for hiding this comment

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

Thanks for the patch!

Left one comment for the defer handle, IIUC.

defer func(u url.URL) {
if err == nil {
defer func(addr string) {
if err == nil || sctx.l == nil {
Copy link
Member

Choose a reason for hiding this comment

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

I think we should pass sctx in the defer function instead of address.

https://go.dev/play/p/ZzmQADcBkJl

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice find!, will send a separate PR with a fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

6 participants