-
Notifications
You must be signed in to change notification settings - Fork 59
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
Named port environment variables not being created #165
Comments
Hi @Strayfe, Thanks for the comment, we will try and look into this! |
@Strayfe thank you for the great issue and details! To summarize, I think I get two actions items from this, please correct me if they are wrong.
We will add this to our planning. Just keep in mind we are a small team. As always we also encourage community to contribute :) |
@elenavillamil that is correct, providing that point 1. As per your note regarding community contributions, I will try to assist where I can! Looking to see the Kubernetes Go code to see what I might be able to transpose into dotnet, we can see the logic flow for how the first port in a service is allocated to the I have located the code where we will need to update the collection of variables being created but in order to create named ports we will require more context to be passed to this method as the name is not currently available to it. I will try to investigate further to see how this information is being passed around currently but as I am not au fait with this code-base don't expect much 😅 . |
Ha, there is actually a comment already to this effect in the code 😛 , it looks like the I am going to sleep now but I will leave you with my current thoughts with a few todo comments still to work out, though this first draft would fix the 2nd point as long as the collection was being allocated in the correct order already: public IDictionary<string, string> CreateEnvVariablesForK8s(WorkloadInfo workloadInfo)
{
var result = new Dictionary<string, string>(workloadInfo.EnvironmentVariables);
foreach (var endpoint in workloadInfo.ReachableEndpoints)
{
if (string.Equals(endpoint.DnsName, DAPR, StringComparison.OrdinalIgnoreCase))
{
// Override the DAPR env variables with the real local ports (that might be different if we neeeded to re-allocate them)
result["DAPR_HTTP_PORT"] = endpoint.Ports[0].LocalPort.ToString(); // TODO (lolodi): this is a hack that relies on the HTTP port to always be the first and GRPC port the second.
result["DAPR_GRPC_PORT"] = endpoint.Ports[1].LocalPort.ToString(); // We should probably name the port pairs (maybe with the env variable that we want to set with them).
} // Once we do that, we can actually stop assigning the DAPR dns name ot this endpoint and just leave it empty, consistently with how the Remote agent works
// because we are using dns name instead of service we have to retrieve it by splitting when needed
// If this ever cause issues we should consider larger refactor where we add serviceName member variable to EndpointInfo class.
var serviceName = endpoint.DnsName
.ToUpperInvariant()
.Split(".")
.First()
.Replace("-", "_")
.Replace(".", "_");
var host = _useKubernetesServiceEnvironmentVariables || string.Equals(endpoint.DnsName, DAPR, StringComparison.OrdinalIgnoreCase)
? endpoint.LocalIP.ToString()
: endpoint.DnsName;
// todo: check that the ports in this collection is allocated in the correct order and immutable
var unnamedPort = _useKubernetesServiceEnvironmentVariables || string.Equals(endpoint.DnsName, DAPR, StringComparison.OrdinalIgnoreCase)
? endpoint.Ports[0].LocalPort
: endpoint.Ports[0].RemotePort;
if (string.Equals(serviceName, "KUBERNETES", StringComparison.OrdinalIgnoreCase))
host = _kubernetesClient.HostName;
// Service Host
result[$"{serviceName}_SERVICE_HOST"] = host;
// Service Port: allocate the first port in the service to the backwards-compatible environment variable in keeping with Kubernetes source code
result[$"{serviceName}_SERVICE_PORT"] = unnamedPort.ToString();
// All named ports (only the first may be unnamed according to Kubernetes source code)
foreach (var portPair in endpoint.Ports)
{
var port = _useKubernetesServiceEnvironmentVariables || string.Equals(endpoint.DnsName, DAPR, StringComparison.OrdinalIgnoreCase)
? portPair.LocalPort
: portPair.RemotePort;
// todo: work out named ports, will need to extend the PortPair class to include Name property and populate it accordingly
var namedPort = string.Empty;
var protocolUpper = portPair.Protocol.ToUpperInvariant();
result[$"{serviceName}_PORT"] = $"{portPair.Protocol}://{host}:{port}";
result[$"{serviceName}_PORT_{port}_{protocolUpper}_PROTO"] = portPair.Protocol;
result[$"{serviceName}_PORT_{port}_{protocolUpper}"] = $"{portPair.Protocol}://{host}:{port}";
result[$"{serviceName}_PORT_{port}_{protocolUpper}_PORT"] = port.ToString();
result[$"{serviceName}_PORT_{port}_{protocolUpper}_ADDR"] = host;
result[$"{serviceName}_SERVICE_PORT_{protocolUpper}"] = port.ToString();
// new named port
result[$"{serviceName}_SERVICE_PORT_{namedPort}"] = port.ToString();
// if this is managed identity with useKubernetesServiceEnvironmentVariables set to true we have to update ms endpoint variable from dns name to ip:port
if (_useKubernetesServiceEnvironmentVariables && string.Equals(serviceName, ManagedIdentity.TargetServiceNameOnLocalMachine, StringComparison.OrdinalIgnoreCase))
result[ManagedIdentity.MSI_ENDPOINT_EnvironmentVariable] = $"http://{host}:{port}/metadata/identity/oauth2/token";
}
}
return result;
} P.S. I have moved some variables about because I did not see a reason for the same variables to be evaluated multiple times in the inner loop when it was not subject to change as part of the outer loop. This new method does breach cognitive complexity but I will see what we can tidy up after I work out point 1. |
Release for this issue should go out next week. Thanks @Strayfe for your contribution! |
Thank you, that is amazing news! |
Apologies, we run into small hipcup on release this week. New ETA for this to be released is next Wednesday. |
Thanks for keeping me in the loop! |
@Strayfe we have a NodeJS and PHP util lib we share for our services. I was able to successfully mitigate this by offsetting from Where we have 3 ports exposed by the service: ports:
- appProtocol: amqp
name: amqp
port: 5672
protocol: TCP
targetPort: 5672
- appProtocol: http
name: management
port: 15672
protocol: TCP
targetPort: 15672
- appProtocol: prometheus.io/metrics
name: prometheus
port: 15692
protocol: TCP
targetPort: 15692 Example from PHP $queuePort = getenv('K8S_QA_RABBITMQ_SERVICE_PORT');
$queuePortOffset = ((int) $queuePort) - 2; |
@garyburgmann thanks for the information. Just in case you may not have noticed I have raised a Pull Request, which has been merged, to update Bridge to Kubernetes to mirror the Kubernetes behaviour which is to use the first port in the service as the You can see the changes here: #173 Please keep in mind that as you maintain a library that offsets the port, you will likely need to update your code when this change goes live (hopefully tomorrow according to elenavillamil's recent comments). For clarity, these ports should now be available as per your example: Re-ordered to first in the list as per Kubernetes source code Named port variables to allow unambiguous port referencing |
Yep, saw the comments above re: the delayed release and just thought I'd share my 2 cents. Thanks a lot for this very handy change @Strayfe 🤘🏻 |
running this with CLI w/ success $ cat /tmp/60add30d.sh | grep K8S_QA_RABBITMQ_SERVICE_PORT_
export K8S_QA_RABBITMQ_SERVICE_PORT_AMQP=55216
export K8S_QA_RABBITMQ_SERVICE_PORT_MANAGEMENT=55217
export K8S_QA_RABBITMQ_SERVICE_PORT_PROMETHEUS=55218
$ cat /tmp/60add30d.sh | grep PORTAL_SERVICE_PORT_
export PORTAL_SERVICE_PORT_HTTP=55102
export PORTAL_SERVICE_PORT_GRPC=55103 |
Hi everyone, just wanted to share quick update here. Fix for this that @Strayfe worked on got deployed today. Please feel free to re-open if you still the issue after upgrading to latest version. |
I am raising this as a new issue as I am not sure I am going to get a response to #64
There seems to be a few discrepancies between the way that Kubernetes and Bridge allocate ports from a service that contains multiple named ports.
There are environment variables created by Kubernetes that reference ports by the name metadata, if it exists, instead of by port which are allocated arbitrarily when using Bridge as the OP described.
Say your microservice has a dependency on a service you have no control over, let's use the bitnami RabbitMQ helm chart as an example, which exposes the following service (trimmed for brevity):
This service has 4 ports described, all with
name
metadata so the ports can be referenced by name in other resources like Ingresses, etc.An example of this would look like:
Now, when we look at one of our RabbitMQ pods in a Kubernetes cluster, we can see environment variables that have been created to reflect each of these ports by name (IP addresses obfuscated to 0.0.0.0):
So, if I wanted to reference my RabbitMQ instance using these environment variables I would take (a) and ideally (b) in my code and allow Bridge to update the variables accordingly:
However, we run into the issue where only the last registered port (in order) will be allocated to the
RABBITMQ_SERVICE_PORT
variable as seen below:In the above image you can see:
(1) - 4x ports mapped, same as in the service but randomly allocated ports on my dev machine
(2) - the last registered port has been allocated to the
RABBITMQ_SERVICE_PORT
variableThere are a few issues here:
As you can see from (b), Kubernetes has mapped the
RABBITMQ_SERVICE_PORT
correctly to the AMQP port (first in the collection) whereas Bridge has allocated the last port in the collection to the variable which is not the AMQP port so I get connection errors in code.Also, as there is no way to pre-determine what port Bridge is going to allocate to (c) when it creates its proxy ports and thus the names of the variables, we have no way to dynamically add these to our code, one moment it could be port 55070, the next run it could be 55011, etc.
Now, you might have noticed at this point that there is a discrepancy highlighted by the existence of (d) which is a port variable declared in Kubernetes using the name metadata instead of a port which would be easier to track in code as we could just request the
RABBITMQ_SERVICE_PORT_AMQP
port and then no matter what port Bridge chose to use for its proxies, we could track it.I am not sure why Bridge does not fetch this variable from the cluster but could it be looked into?
Originally posted by @Strayfe in #64 (comment)
The text was updated successfully, but these errors were encountered: