-
Notifications
You must be signed in to change notification settings - Fork 91
Implement egress proxy for external services #463
Conversation
proxy/envoy/config.go
Outdated
if service.External { | ||
for _ , route := range routes { | ||
for _ , cluster := range route.clusters { | ||
cluster.ServiceName = "" |
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.
Why empty the service name in the cluster?
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.
ServiceName is only for clusters of type sds
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.
I wonder if we should use SDS for egress proxies. Probably not in this PR.
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.
Yes I was debating about whether to use a default URL from MeshConfig or pointing it to SDS. I chose the meshconfig option because I figured otherwise I would have to either store the egress model.Service or cache it somehow so I could reliably generate a ServiceName
for the cluster
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.
We cannot use SDS for egress proxies. Because SDS cannot serve DNS names. Only IP/ports are allowed. If we convince Matt that SDS could allow logical_dns and strict_dns, then we might use SDS for egress as well.
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.
@rshriram this is for the sidecar proxy config gen. SDS in this case would return the IP of the egress service. Egress proxy config gen would NOT use SDS because it will have to statically define the cluster with hosts (i.e. google.com, etc)
proxy/envoy/config.go
Outdated
if service.External { | ||
for _ , route := range routes { | ||
for _ , cluster := range route.clusters { | ||
cluster.ServiceName = "" |
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.
I wonder if we should use SDS for egress proxies. Probably not in this PR.
proxy/envoy/resources.go
Outdated
@@ -73,6 +76,7 @@ type MeshConfig struct { | |||
var ( | |||
// DefaultMeshConfig configuration | |||
DefaultMeshConfig = &MeshConfig{ | |||
EgressAddress: "istio-egress:80", |
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.
EgressProxyAddress
(we'll converge with ingress eventually)
proxy/envoy/resources.go
Outdated
@@ -210,6 +214,8 @@ type HTTPRoute struct { | |||
// faults contains the set of referenced faults in the route; the field is special | |||
// and used only to aggregate fault filter information after composing routes | |||
faults []*HTTPFilter | |||
|
|||
AutoHostRewrite bool `json:"auto_host_rewrite,omitempty"` |
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.
can you move it up above private fields?
platform/kube/conversion.go
Outdated
if svc.Spec.ClusterIP != "" && svc.Spec.ClusterIP != v1.ClusterIPNone { | ||
addr = svc.Spec.ClusterIP | ||
} | ||
|
||
if svc.Spec.ExternalName != "" { |
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.
You should compare svc.Spec
service type, like in https://github.com/istio/manager/blob/master/platform/kube/controller.go#L520. There is some dead code there that needs to be updated as well.
Jenkins job manager/presubmit passed |
Codecov Report
@@ Coverage Diff @@
## master #463 +/- ##
==========================================
- Coverage 76.5% 76.21% -0.29%
==========================================
Files 26 26
Lines 3413 3376 -37
==========================================
- Hits 2611 2573 -38
- Misses 608 612 +4
+ Partials 194 191 -3
Continue to review full report at Codecov.
|
Jenkins job manager/presubmit passed |
2 similar comments
Jenkins job manager/presubmit passed |
Jenkins job manager/presubmit passed |
Jenkins job istio/manager-pr-master passed |
proxy/envoy/egress.go
Outdated
rConfig := &HTTPRouteConfig{VirtualHosts: vhosts} | ||
|
||
listener := &Listener{ | ||
Address: fmt.Sprintf("tcp://%s:80", WildcardAddress), |
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.
Hard coding port here? Isn't istio egress service port configurable ?
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.
This is egress proxy port, not the service port. @GregHanson please add a comment saying that service must have exactly one port or they get conflated.
proxy/envoy/egress.go
Outdated
}, | ||
} | ||
|
||
//TODO |
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.
Doesn't apply to egress right? Please remove comment. But add a comment related to tcp service
Jenkins job manager/presubmit passed |
1 similar comment
Jenkins job manager/presubmit passed |
Jenkins job manager/e2e-smoketest passed |
Jenkins job manager/presubmit passed |
Jenkins job manager/e2e-smoketest passed |
Jenkins job manager/presubmit passed |
Jenkins job manager/e2e-smoketest passed |
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.
Overall LGTM. I suggest we add a field to hold the external name instead of overloading Address
. This will be needed for TCP external services.
proxy/envoy/config.go
Outdated
@@ -269,6 +270,22 @@ func buildOutboundHTTPRoutes(instances []*model.ServiceInstance, services []*mod | |||
routes = append(routes, buildDefaultRoute(cluster)) | |||
} | |||
|
|||
if service.External { | |||
for _, route := range routes { | |||
route.HostRewrite = service.Hostname |
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.
Is this needed? Isn't pod already using service hostname to address an external service?
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.
it was a design choice. For example the proxy in the pod matches on this list of domains:
"httpbin:80",
"httpbin",
"httpbin.default:80",
"httpbin.default",
"httpbin.default.svc:80",
"httpbin.default.svc",
"httpbin.default.svc.cluster:80",
"httpbin.default.svc.cluster",
"httpbin.default.svc.cluster.local:80",
"httpbin.default.svc.cluster.local",
"httpbin.org:80",
"httpbin.org"
By setting route.HostRewrite to a known value, the egress proxy explicitly only listens/matches for one of those domains:
"httpbin.org"
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.
OK, I see. I don't think "httpbin.org" should be in that list. This doesn't sound right @rshriram
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.
what do you mean? The application addresses the external service directly by the name and not via the kubernetes version of the service (httpbin.default.svc.cluster.local).
|
||
// EgressConfig defines information for engress | ||
type EgressConfig struct { | ||
Namespace string |
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.
dead code?
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.
This struct is still used as the input struct for generateEgress()
proxy/envoy/egress.go
Outdated
} | ||
|
||
func generateEgress(conf *EgressConfig) *Config { | ||
|
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.
unnecessary white space
proxy/envoy/egress.go
Outdated
rConfig := &HTTPRouteConfig{VirtualHosts: vhosts} | ||
|
||
listener := &Listener{ | ||
Address: fmt.Sprintf("tcp://%s:80", WildcardAddress), |
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.
This is egress proxy port, not the service port. @GregHanson please add a comment saying that service must have exactly one port or they get conflated.
proxy/envoy/egress.go
Outdated
} | ||
|
||
case model.ProtocolTCP, model.ProtocolHTTPS: | ||
// TODO |
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.
drop this clause, so that default case will print a warning.
proxy/envoy/egress.go
Outdated
route.clusters = append(route.clusters, cluster) | ||
|
||
host = &VirtualHost{ | ||
Name: buildEgressClusterName(svc.Address, servicePort.Port), |
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.
reuse cluster.Name
proxy/envoy/egress.go
Outdated
route := &HTTPRoute{ | ||
Prefix: "/", | ||
Cluster: buildEgressClusterName(svc.Address, servicePort.Port), | ||
AutoHostRewrite: true, |
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.
Should we just put HostRewrite
to the external name? It's known ahead of time based on the cluster definition.
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.
In CloudFoundry case: catalog-1.mystore.com
, catalog-2.mystore.com
, catalog-3.mystore.com
are all under under catalog.mystore.com
Although the current egress implementation does not support this yet, I tried to model the envoy config with this in mind. With AutoHostRewrite
Host header will be set to the value of the URL from the host chosen in the cluster
test/mock/service.go
Outdated
WorldService = MakeService("world.default.svc.cluster.local", "10.2.0.0") | ||
ExtHTTPService = MakeExternalHTTPService("httpbin.default.svc.cluster.local", | ||
"httpbin.org", "10.3.0.0") | ||
ExtHTTPSService = MakeExternalHTTPSService("httpsbin.default.svc.cluster.local", |
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.
please remove this until it is used.
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.
I left it in there originally because until external https services are supported - it should not appear in RDS/CDS/egress outputs. And until they are supported, if someone pushes a change to the config generation logic - I wanted there to be something in place to catch it
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.
OK, so it's a negative test. That's fine then.
platform/kube/conversion.go
Outdated
if svc.Spec.ClusterIP != "" && svc.Spec.ClusterIP != v1.ClusterIPNone { | ||
addr = svc.Spec.ClusterIP | ||
} | ||
|
||
if svc.Spec.Type == v1.ServiceTypeExternalName && svc.Spec.ExternalName != "" { | ||
addr = svc.Spec.ExternalName |
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.
I think you should add a field to Service
called ExternalName
to hold this value. Address is needed for TCP/HTTPS routing for the in-cluster IP address.
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.
Do you mean adding it to model.Service ? If so, then it simply conflates the model. Services already have a hostname in the model and there is nothing in the model that says a service is in cluster or not. So adding an ExternalName makes little sense from a generic model perspective.
If anything, I would add a type field to the model.Service, that can qualify a service as either "internal/external", deployment mode as a boolean "user-facing or not". This would let ingress/gateway Envoy pick up only user-facing services of internal Type, non-k8s environments), egress Envoy pick external services while other envoys can pickup internal services and external service (with a host rewrite to egress envoy).
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.
I can also use service.Hostname to store the ExternalName value and just not touch service.Address in the external case . I originally chose to overwrite service.Address with ExternalName because I wanted to preserve the service.default.local.svc
value because the logic for generating it was not exposed.
@@ -79,6 +115,9 @@ func MakeInstance(service *model.Service, port *model.Port, version int) *model. | |||
// MakeIP creates a fake IP address for a service and instance version | |||
func MakeIP(service *model.Service, version int) string { | |||
ip := net.ParseIP(service.Address).To4() | |||
if ip == nil { |
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.
This isn't needed if you add a field for the external name.
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.
as a general istio user wishing to use an external service - are they required to know the hostname (google.com) and the IP it resolves to? Or from a coding standpoint then, if an external service is specified how do we populate the IP field, or would we just leave IP/Address as an empty string in this case? And if left empty, we would still need this ip == nil
check in the code or else the next few lines trigger an index out of bounds exception
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.
I think this will be the case until we start using Endpoints for external services
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.
I don't think adding ExternalName to the service is a good idea as it conflates the model.
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.
.
The way I see it, |
ExternalName service is a platform specific implementation detail - with respect to kubernetes. The way I see it, external service is just an extension of the Istio mesh, allowing services outside the mesh to be represented in Istio. Doing the external name exposes the internal implementation detail of having an explicit istio-egress service. If we move towards a model where the sidecars directly route to external services (after talking to mixer), then this whole thing becomes redundant. |
Jenkins job manager/presubmit passed |
Jenkins job manager/e2e-smoketest passed |
Jenkins job manager/presubmit passed |
Jenkins job manager/e2e-smoketest passed |
Jenkins job manager/presubmit passed |
Jenkins job manager/e2e-smoketest passed |
Jenkins job manager/presubmit passed |
Jenkins job manager/e2e-smoketest passed |
Assumptions:
See issue #67 for discussion