Implement h2c with backend
This commit is contained in:
parent
83e09acc9f
commit
9420308667
6 changed files with 126 additions and 52 deletions
|
@ -345,16 +345,22 @@ Here is an example of backends and servers definition:
|
||||||
[backends.backend2]
|
[backends.backend2]
|
||||||
# ...
|
# ...
|
||||||
[backends.backend2.servers.server1]
|
[backends.backend2.servers.server1]
|
||||||
url = "http://172.17.0.4:80"
|
url = "https://172.17.0.4:443"
|
||||||
weight = 1
|
weight = 1
|
||||||
[backends.backend2.servers.server2]
|
[backends.backend2.servers.server2]
|
||||||
url = "http://172.17.0.5:80"
|
url = "https://172.17.0.5:443"
|
||||||
weight = 2
|
weight = 2
|
||||||
|
[backends.backend3]
|
||||||
|
# ...
|
||||||
|
[backends.backend3.servers.server1]
|
||||||
|
url = "h2c://172.17.0.6:80"
|
||||||
|
weight = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
- Two backends are defined: `backend1` and `backend2`
|
- Two backends are defined: `backend1` and `backend2`
|
||||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1`.
|
- `backend1` will forward the traffic to two servers: `172.17.0.2:80` with weight `10` and `172.17.0.3:80` with weight `1`.
|
||||||
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2`.
|
- `backend2` will forward the traffic to two servers: `172.17.0.4:443` with weight `1` and `172.17.0.5:443` with weight `2` both using TLS.
|
||||||
|
- `backend3` will forward the traffic to: `172.17.0.6:80` with weight `1` using HTTP2 without TLS.
|
||||||
|
|
||||||
#### Load-balancing
|
#### Load-balancing
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 146 KiB |
|
@ -3,26 +3,13 @@
|
||||||
This section explains how to use Traefik as reverse proxy for gRPC application with self-signed certificates.
|
This section explains how to use Traefik as reverse proxy for gRPC application with self-signed certificates.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
As gRPC needs HTTP2, we need HTTPS certificates on both gRPC Server and Træfik.
|
As gRPC needs HTTP2, we need HTTPS certificates on Træfik.
|
||||||
|
For exchanges with the backend, we will use h2c (HTTP2 on HTTP without TLS)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="/img/grpc.svg" alt="gRPC architecture" title="gRPC architecture" />
|
<img src="/img/grpc.svg" alt="gRPC architecture" title="gRPC architecture" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## gRPC Server certificate
|
|
||||||
|
|
||||||
In order to secure the gRPC server, we generate a self-signed certificate for backend url:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./backend.key -out ./backend.cert
|
|
||||||
```
|
|
||||||
|
|
||||||
That will prompt for information, the important answer is:
|
|
||||||
|
|
||||||
```
|
|
||||||
Common Name (e.g. server FQDN or YOUR name) []: backend.local
|
|
||||||
```
|
|
||||||
|
|
||||||
## gRPC Client certificate
|
## gRPC Client certificate
|
||||||
|
|
||||||
Generate your self-signed certificate for frontend url:
|
Generate your self-signed certificate for frontend url:
|
||||||
|
@ -44,9 +31,6 @@ At last, we configure our Træfik instance to use both self-signed certificates.
|
||||||
```toml
|
```toml
|
||||||
defaultEntryPoints = ["https"]
|
defaultEntryPoints = ["https"]
|
||||||
|
|
||||||
# For secure connection on backend.local
|
|
||||||
rootCAs = [ "./backend.cert" ]
|
|
||||||
|
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":4443"
|
address = ":4443"
|
||||||
|
@ -64,8 +48,8 @@ rootCAs = [ "./backend.cert" ]
|
||||||
[backends]
|
[backends]
|
||||||
[backends.backend1]
|
[backends.backend1]
|
||||||
[backends.backend1.servers.server1]
|
[backends.backend1.servers.server1]
|
||||||
# Access on backend with HTTPS
|
# Access on backend with h2c
|
||||||
url = "https://backend.local:8080"
|
url = "h2c://backend.local:8080"
|
||||||
|
|
||||||
|
|
||||||
[frontends]
|
[frontends]
|
||||||
|
@ -76,40 +60,25 @@ rootCAs = [ "./backend.cert" ]
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
With some backends, the server URLs use the IP, so you may need to configure `insecureSkipVerify` instead of the `rootCAS` to activate HTTPS without hostname verification.
|
For provider with label, you will have to specify the `traefik.protocol=h2c`
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
We don't need specific configuration to use gRPC in Træfik, we just need to be careful that all the exchanges (between client and Træfik, and between Træfik and backend) are HTTPS communications because gRPC uses HTTP2.
|
We don't need specific configuration to use gRPC in Træfik, we just need to be careful that exchanges between client and Træfik are HTTPS communications.
|
||||||
|
For exchanges between Træfik and backend, you need to use `h2c` protocol, or use HTTPS communications to have HTTP2.
|
||||||
|
|
||||||
## A gRPC example in go
|
## A gRPC example in go
|
||||||
|
|
||||||
We will use the gRPC greeter example in [grpc-go](https://github.com/grpc/grpc-go/tree/master/examples/helloworld)
|
We will use the gRPC greeter example in [grpc-go](https://github.com/grpc/grpc-go/tree/master/examples/helloworld)
|
||||||
|
|
||||||
!!! warning
|
We can keep the Server example as is with the h2c protocol
|
||||||
In order to use this gRPC example, we need to modify it to use HTTPS
|
|
||||||
|
|
||||||
So we modify the "gRPC server example" to use our own self-signed certificate:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// ...
|
// ...
|
||||||
|
lis, err := net.Listen("tcp", port)
|
||||||
// Read cert and key file
|
|
||||||
BackendCert, _ := ioutil.ReadFile("./backend.cert")
|
|
||||||
BackendKey, _ := ioutil.ReadFile("./backend.key")
|
|
||||||
|
|
||||||
// Generate Certificate struct
|
|
||||||
cert, err := tls.X509KeyPair(BackendCert, BackendKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to parse certificate: %v", err)
|
log.Fatalf("failed to listen: %v", err)
|
||||||
}
|
}
|
||||||
|
var s *grpc.Server = grpc.NewServer()
|
||||||
// Create credentials
|
|
||||||
creds := credentials.NewServerTLSFromCert(&cert)
|
|
||||||
|
|
||||||
// Use Credentials in gRPC server options
|
|
||||||
serverOption := grpc.Creds(creds)
|
|
||||||
var s *grpc.Server = grpc.NewServer(serverOption)
|
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
|
|
||||||
pb.RegisterGreeterServer(s, &server{})
|
pb.RegisterGreeterServer(s, &server{})
|
||||||
|
@ -118,6 +87,10 @@ err := s.Serve(lis)
|
||||||
// ...
|
// ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
In order to use this gRPC example, we need to modify it to use HTTPS
|
||||||
|
|
||||||
|
|
||||||
Next we will modify gRPC Client to use our Træfik self-signed certificate:
|
Next we will modify gRPC Client to use our Træfik self-signed certificate:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
@ -147,4 +120,3 @@ r, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: name})
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
29
integration/fixtures/grpc/config_h2c.toml
Normal file
29
integration/fixtures/grpc/config_h2c.toml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
defaultEntryPoints = ["https"]
|
||||||
|
|
||||||
|
rootCAs = [ """{{ .CertContent }}""" ]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":4443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
certFile = """{{ .CertContent }}"""
|
||||||
|
keyFile = """{{ .KeyContent }}"""
|
||||||
|
|
||||||
|
|
||||||
|
[api]
|
||||||
|
|
||||||
|
[file]
|
||||||
|
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.servers.server1]
|
||||||
|
url = "h2c://127.0.0.1:{{ .GRPCServerPort }}"
|
||||||
|
weight = 1
|
||||||
|
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend1]
|
||||||
|
backend = "backend1"
|
||||||
|
[frontends.frontend1.routes.test_1]
|
||||||
|
rule = "Host:127.0.0.1"
|
|
@ -67,6 +67,15 @@ func startGRPCServer(lis net.Listener, server *myserver) error {
|
||||||
helloworld.RegisterGreeterServer(s, server)
|
helloworld.RegisterGreeterServer(s, server)
|
||||||
return s.Serve(lis)
|
return s.Serve(lis)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func starth2cGRPCServer(lis net.Listener, server *myserver) error {
|
||||||
|
s := grpc.NewServer()
|
||||||
|
defer s.Stop()
|
||||||
|
|
||||||
|
helloworld.RegisterGreeterServer(s, server)
|
||||||
|
return s.Serve(lis)
|
||||||
|
}
|
||||||
|
|
||||||
func getHelloClientGRPC() (helloworld.GreeterClient, func() error, error) {
|
func getHelloClientGRPC() (helloworld.GreeterClient, func() error, error) {
|
||||||
roots := x509.NewCertPool()
|
roots := x509.NewCertPool()
|
||||||
roots.AppendCertsFromPEM(LocalhostCert)
|
roots.AppendCertsFromPEM(LocalhostCert)
|
||||||
|
@ -137,12 +146,54 @@ func (s *GRPCSuite) TestGRPC(c *check.C) {
|
||||||
// wait for Traefik
|
// wait for Traefik
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:127.0.0.1"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:127.0.0.1"))
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
var response string
|
var response string
|
||||||
err = try.Do(1*time.Second, func() error {
|
err = try.Do(1*time.Second, func() error {
|
||||||
response, err = callHelloClientGRPC("World")
|
response, err = callHelloClientGRPC("World")
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(response, check.Equals, "Hello World")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GRPCSuite) TestGRPCh2c(c *check.C) {
|
||||||
|
lis, err := net.Listen("tcp", ":0")
|
||||||
|
_, port, err := net.SplitHostPort(lis.Addr().String())
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := starth2cGRPCServer(lis, &myserver{})
|
||||||
|
c.Log(err)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
}()
|
||||||
|
|
||||||
|
file := s.adaptFile(c, "fixtures/grpc/config_h2c.toml", struct {
|
||||||
|
CertContent string
|
||||||
|
KeyContent string
|
||||||
|
GRPCServerPort string
|
||||||
|
}{
|
||||||
|
CertContent: string(LocalhostCert),
|
||||||
|
KeyContent: string(LocalhostKey),
|
||||||
|
GRPCServerPort: port,
|
||||||
|
})
|
||||||
|
|
||||||
|
defer os.Remove(file)
|
||||||
|
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||||
|
defer display(c)
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// wait for Traefik
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:127.0.0.1"))
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
var response string
|
||||||
|
err = try.Do(1*time.Second, func() error {
|
||||||
|
response, err = callHelloClientGRPC("World")
|
||||||
|
return err
|
||||||
|
})
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(response, check.Equals, "Hello World")
|
c.Assert(response, check.Equals, "Hello World")
|
||||||
}
|
}
|
||||||
|
@ -179,12 +230,12 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) {
|
||||||
// wait for Traefik
|
// wait for Traefik
|
||||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:127.0.0.1"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:127.0.0.1"))
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
var response string
|
var response string
|
||||||
err = try.Do(1*time.Second, func() error {
|
err = try.Do(1*time.Second, func() error {
|
||||||
response, err = callHelloClientGRPC("World")
|
response, err = callHelloClientGRPC("World")
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(response, check.Equals, "Hello World")
|
c.Assert(response, check.Equals, "Hello World")
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,6 +145,15 @@ func NewServer(globalConfiguration configuration.GlobalConfiguration, provider p
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type h2cTransportWrapper struct {
|
||||||
|
*http2.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *h2cTransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
return t.Transport.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
// createHTTPTransport creates an http.Transport configured with the GlobalConfiguration settings.
|
// createHTTPTransport creates an http.Transport configured with the GlobalConfiguration settings.
|
||||||
// For the settings that can't be configured in Traefik it uses the default http.Transport settings.
|
// For the settings that can't be configured in Traefik it uses the default http.Transport settings.
|
||||||
// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost
|
// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost
|
||||||
|
@ -168,6 +177,16 @@ func createHTTPTransport(globalConfiguration configuration.GlobalConfiguration)
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transport.RegisterProtocol("h2c", &h2cTransportWrapper{
|
||||||
|
Transport: &http2.Transport{
|
||||||
|
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
|
return net.Dial(netw, addr)
|
||||||
|
},
|
||||||
|
AllowHTTP: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
if globalConfiguration.ForwardingTimeouts != nil {
|
if globalConfiguration.ForwardingTimeouts != nil {
|
||||||
transport.ResponseHeaderTimeout = time.Duration(globalConfiguration.ForwardingTimeouts.ResponseHeaderTimeout)
|
transport.ResponseHeaderTimeout = time.Duration(globalConfiguration.ForwardingTimeouts.ResponseHeaderTimeout)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue