Skip to content
This repository has been archived by the owner on Jun 14, 2018. It is now read-only.

Implement egress proxy for external services #463

Merged
merged 20 commits into from
Apr 13, 2017
Merged

Implement egress proxy for external services #463

merged 20 commits into from
Apr 13, 2017

Conversation

GregHanson
Copy link
Member

@GregHanson GregHanson commented Apr 6, 2017

Assumptions:

  • external services can have only one port, and that port will be either 80 or 443
  • Support for external services of type https will be addressed in a later pull request

See issue #67 for discussion

if service.External {
for _ , route := range routes {
for _ , cluster := range route.clusters {
cluster.ServiceName = ""
Copy link
Member

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?

Copy link
Member Author

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

Copy link
Contributor

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.

Copy link
Member Author

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

Copy link
Member

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.

Copy link
Member Author

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)

if service.External {
for _ , route := range routes {
for _ , cluster := range route.clusters {
cluster.ServiceName = ""
Copy link
Contributor

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.

@@ -73,6 +76,7 @@ type MeshConfig struct {
var (
// DefaultMeshConfig configuration
DefaultMeshConfig = &MeshConfig{
EgressAddress: "istio-egress:80",
Copy link
Contributor

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)

@@ -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"`
Copy link
Contributor

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?

if svc.Spec.ClusterIP != "" && svc.Spec.ClusterIP != v1.ClusterIPNone {
addr = svc.Spec.ClusterIP
}

if svc.Spec.ExternalName != "" {
Copy link
Contributor

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.

@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@codecov
Copy link

codecov bot commented Apr 7, 2017

Codecov Report

Merging #463 into master will decrease coverage by 0.28%.
The diff coverage is 85.71%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
proxy/envoy/resources.go 78.81% <ø> (-8.93%) ⬇️
proxy/envoy/config.go 91.28% <100%> (+0.46%) ⬆️
test/mock/service.go 91.2% <100%> (+0.99%) ⬆️
proxy/envoy/route.go 91.61% <100%> (ø) ⬆️
platform/kube/conversion.go 81.6% <100%> (+0.32%) ⬆️
proxy/envoy/egress.go 78.82% <78.82%> (ø)
test/mock/secret.go 0% <0%> (-50%) ⬇️
proxy/envoy/ingress.go 64.6% <0%> (-6.83%) ⬇️
proxy/envoy/discovery.go 73.65% <0%> (-5.17%) ⬇️
... and 7 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bd53495...9576d0b. Read the comment docs.

@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

2 similar comments
@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@istio-testing
Copy link
Contributor

Jenkins job istio/manager-pr-master passed

rConfig := &HTTPRouteConfig{VirtualHosts: vhosts}

listener := &Listener{
Address: fmt.Sprintf("tcp://%s:80", WildcardAddress),
Copy link
Member

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 ?

Copy link
Contributor

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.

},
}

//TODO
Copy link
Member

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

@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

1 similar comment
@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@istio-testing
Copy link
Contributor

Jenkins job manager/e2e-smoketest passed

@GregHanson GregHanson changed the title [WIP] first pass at egress service implementation Implement egress proxy for external services Apr 11, 2017
@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@istio-testing
Copy link
Contributor

Jenkins job manager/e2e-smoketest passed

@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@istio-testing
Copy link
Contributor

Jenkins job manager/e2e-smoketest passed

Copy link
Contributor

@kyessenov kyessenov left a 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.

@@ -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
Copy link
Contributor

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?

Copy link
Member Author

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"

Copy link
Contributor

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

Copy link
Member

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
Copy link
Contributor

Choose a reason for hiding this comment

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

dead code?

Copy link
Member Author

@GregHanson GregHanson Apr 12, 2017

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()

}

func generateEgress(conf *EgressConfig) *Config {

Copy link
Contributor

Choose a reason for hiding this comment

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

unnecessary white space

rConfig := &HTTPRouteConfig{VirtualHosts: vhosts}

listener := &Listener{
Address: fmt.Sprintf("tcp://%s:80", WildcardAddress),
Copy link
Contributor

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.

}

case model.ProtocolTCP, model.ProtocolHTTPS:
// TODO
Copy link
Contributor

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.

route.clusters = append(route.clusters, cluster)

host = &VirtualHost{
Name: buildEgressClusterName(svc.Address, servicePort.Port),
Copy link
Contributor

Choose a reason for hiding this comment

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

reuse cluster.Name

route := &HTTPRoute{
Prefix: "/",
Cluster: buildEgressClusterName(svc.Address, servicePort.Port),
AutoHostRewrite: true,
Copy link
Contributor

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.

Copy link
Member Author

@GregHanson GregHanson Apr 12, 2017

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

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",
Copy link
Contributor

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.

Copy link
Member Author

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

Copy link
Contributor

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.

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
Copy link
Contributor

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.

Copy link
Member

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).

Copy link
Member Author

@GregHanson GregHanson Apr 12, 2017

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 {
Copy link
Contributor

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.

Copy link
Member Author

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

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 this will be the case until we start using Endpoints for external services

Copy link
Member

@rshriram rshriram left a 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.

Copy link
Member

@rshriram rshriram left a comment

Choose a reason for hiding this comment

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

.

@kyessenov
Copy link
Contributor

The way I see it, ExternalName service is still a regular Istio service with a hostname (cluster-local), an IP address, but no actual pods. Instead, the service is redirected to out-of-cluster DNS name. Logically, this means external name service is a subtype of a service, so we need to add fields to express this in golang.

@rshriram
Copy link
Member

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.

@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@istio-testing
Copy link
Contributor

Jenkins job manager/e2e-smoketest passed

@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@istio-testing
Copy link
Contributor

Jenkins job manager/e2e-smoketest passed

@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@istio-testing
Copy link
Contributor

Jenkins job manager/e2e-smoketest passed

@istio-testing
Copy link
Contributor

Jenkins job manager/presubmit passed

@istio-testing
Copy link
Contributor

Jenkins job manager/e2e-smoketest passed

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants