-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Che server should connect to ws-agent on internal URL (#2030) #2837
Conversation
Can one of the admins verify this patch? |
This improvement addresses issues on certain linux systems where Che does not work with iptables or firewalld. By connecting over the internal address, communication flow will not go through the firewall which blocks access. |
-[ ] Requires updates to the Networking docs for Eclipse Che page to explain the scenario. |
ci-build |
Build # 747 - FAILED Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/747/ to view the results. |
I've rebased this PR, and dropped a commit that fixed an issue that has been since fixed in master (see d74b24f). |
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 add fixes in accordance to comments
* @return true if {@code CHE_DOCKER_USE_INTERNAL_CONTAINER_ADDRESS} is "true", false otherwise. | ||
*/ | ||
protected boolean useInternalContainerAddresses() { | ||
String useInternalContainerAddresses = System.getenv(CHE_DOCKER_USE_INTERNAL_CONTAINER_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.
In Che we use system variables in a different way. We declare property from DI container and it should use environment variable if it is needed.
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.
Sorry, could you clarify this point? Do you mean that it is preferable to control this behaviour through a property defined in che.properties
?
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.
Exactly! And it can also be configured using env variable that match rules of CHE of conversion env variable into property.
The idea is that we use only named injection in Java code and CheBootstrap handles injection of all properties and environment variables and has everything needed to improve code readability.
If you want to know more about that, please, ask.
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.
Thank you, that makes a lot of sense! I've updated the PR to use a property (currently called che.docker.ip.useinternaladdress
-- open to suggestions).
A few questions regarding the conversion of env vars to properties though:
-
in
CheBootstrap
, I see that env vars are converted by lowercasing them and replacing underscores with periods. However, some of the properties have underscores in their name, which makes it impossible to set them with environment variables, and prevents the new property here from having underscores (che.docker.ip.use_internal_address
would make more sense). -
In
LocalDockerInstanceRuntimeInfo
, it looks like this convention is ignored: the methodsexternalHostnameWithPrecedence
andinternalHostnameWithPrecedence
use environment variables to override their behavior -- is this intentional or should it be fixed?
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 submitted a PR to fix that. I'm going to merge it soon. Configuring of named variable with underscore in name doesn't work if it was set using environment variables #2454
- Probably it was implemented before we merged feature with automatic conversion. Or we missed it in PR review.
I believe that it should be fixed. @amisevsk If you want you can fix it also.
@TylerJewell should env variables be renamed to match properties names or it is better to try to use new property aliases feature?
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.
@garagatyi Thanks for pointing me to that PR -- I've renamed the property to che.docker.ip.use_internal_address
for readability. This has the side effect of not being able to override the property with an environment variable until your change is merged.
I've modified LocalDockerInstanceRuntimeInfo
to not use environment variables anymore and updated the docs. Previously, the properties che.docker.ip
and che.docker.ip.external
were overridden through both the environment variable CHE_DOCKER_IP
/CHE_DOCKER_IP_EXTERNAL
but also through CHE_DOCKER_MACHINE_HOST
and CHE_DOCKER_MACHINE_HOST_EXTERNAL
. With the change only the former has an effect.
@@ -310,16 +316,33 @@ public String projectsRoot() { | |||
protected Map<String, ServerImpl> getServersWithFilledPorts(final String externalHostame, final String internalHostname, final Map<String, List<PortBinding>> exposedPorts) { | |||
final HashMap<String, ServerImpl> servers = new LinkedHashMap<>(); | |||
|
|||
boolean useMappedPorts = true; | |||
if (useInternalContainerAddresses()) { |
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 is better to do this inside of constructor to keep main code clean
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.
Moved.
boolean useMappedPorts = true; | ||
if (useInternalContainerAddresses()) { | ||
if (info.getNetworkSettings() != null && internalHostname.equals(info.getNetworkSettings().getIpAddress())) { | ||
useMappedPorts = false; |
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 elaborate why we should care about internalHostname field if usage of IP address provided by Docker is set by configuration?
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 was meant mostly as a sanity check, although even as that I'm not sure it's handled as well as it could be. internalHostname
is still the address used to communicate with wsagent, and I was concerned that it may be possible to create an instance of DockerInstanceRuntimeInfo
with internalHostname
not matching info.getNetworkSettings().getIpAddress()
, in which case contacting wsagent would fail even if internalHostname
is valid.
If you feel that it's not a concern, I will remove 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.
My point that if someone configured property/env variable that enables useInternalContainerAddresses mode then he definitely want to use it. And If it does match internalHostname then this mode doesn't make sense.
So I treat this mode as an alternative to default mode with set internalHostname.
Please correct me if I misunderstood the goal of this contribution.
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're right, that was the goal. I'll remove this check and rename the variable for clarity.
if (useMappedPorts) { | ||
internalHostnameAndPort = internalHostname + ":" + externalPort; | ||
} else { | ||
String internalPort = portEntry.getKey().split("/")[0]; |
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 is better to use portProtocol variable here instead of portEntry.getKey()
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.
Done.
2504e74
to
a0edb9c
Compare
Add ability to make che-server use internal address of workspaces when che-server is running in docker through property `che.docker.ip.useinternaladdress` or environment variable CHE_DOCKER_IP_USEINTERNALADDRESS. When property is set to "true", internal address is set to the internal address of the relevant wsagent docker container. Otherwise, behavior is unchanged. Signed-off-by: Angel Misevski <[email protected]>
String internalHostnameAndPort; | ||
if (useInternalAddress) { | ||
String internalPort = portProtocol.split("/")[0]; | ||
internalHostnameAndPort = internalHostname + ":" + internalPort; |
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 that case it is supposed that browser will connect to ephemeral port or exposed port? I'm asking because as far as I remember it should be exposed port, but I'm not sure.
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.
To my understanding, the browser will use the exposed port; the internalHostname and internalPort should be used only be che-server.
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 really mean exposed port or ephemeral port which is mapped to exposed port?
@l0rd Maybe you can help to understand how this workflow is supposed to work? I know you explain such things very clear.
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.
Let's make an example. wsagent is available on ports:
- 4401 on docker0 network (let's call it the exposed port)
- 32801 on host network (let's call it the ephemeral port).
Browsers use address: externalHostname
:32801
Che server used address internalHostname
:32801 (before this PR)
After this PR Che server should use address containerIP
:4401
The difference is between ephemeral and exposed ports but also between internalHostname
and containerIP
:
internalHostname
= the hostname of the host where docker is running and in most cases is identical toexternalHostname
containerIP
= is the IP address of the workspace container, it's an IP of the docker0 network (you can get this IP address usingdocker inspect -f '{{ .NetworkSettings.IPAddress }}' <containerID>
).
Looking to the code of this PR it seems to me internalHostname
is still used by the Che server. containerIP
should be used instead.
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.
@amisevsk there is a typo in variable externalHostame
. You haven't introduced that (that was probably me 😊) but it would be cool if you could fix it => externalHostname
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.
@l0rd I've fixed the typo now. Could you elaborate more on the issue of containerIP
vs internalHostname
?
@garagatyi Sorry for the confusion -- I meant exposed port in the sense of "exposed to the outside world", e.g. Docker would map the container at 172.17.0.2 with port 4401 (docker0 network) to 172.17.0.1 (address of docker0 network) with port 32771. The browser should contact the container at 172.17.0.1:32771 while the che-server should use 172.17.0.2:4401. I had my definition of exposed and ephemeral backwards.
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.
@amisevsk I understand now that internaHostname
can be either containerIP
or internalHostname
here. The logic to set the right is in class LocalDockerInstanceRuntimeInfo
.
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.
* <p>Value of external hostname can be retrieved from property ${code machine.docker.local_node_host.external} or | ||
* from environment variable {@code CHE_DOCKER_MACHINE_HOST_EXTERNAL}.<br> | ||
* Environment variables have higher priority. | ||
* <p>If environment variable {@code CHE_DOCKER_IP_USE__INTERNAL__ADDRESS} or |
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.
Javadocs should use properties for explanation of usage. Whereas dependency container can inject it in several ways.
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've removed references to environment variables in the javadoc.
@@ -142,6 +142,12 @@ che.docker.ip=NULL | |||
# This is unusual, but happens for example in Docker for Mac when containers are in a VM. | |||
che.docker.ip.external=NULL | |||
|
|||
# If true, then uses the internal address and port of workspace Docker containers (i.e. within the Docker | |||
# Docker network) instead of the address and port provided by the Docker daemon. May be necessary if the |
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 find confusing the sentence "instead of the address and port provided by the Docker daemon". In fact the Docker daemon never provides the address of the container as far as I know. It does maps the ports of a container (that are exposed to the docker0 network) to some corresponding ephemeral ports exposed to the host network. I would rather change it to "instead of the docker host address and the port provided by the Docker daemon".
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've updated the sentence.
String internalHostnameAndPort; | ||
if (useInternalAddress) { | ||
String internalPort = portProtocol.split("/")[0]; | ||
internalHostnameAndPort = internalHostname + ":" + internalPort; |
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.
Let's make an example. wsagent is available on ports:
- 4401 on docker0 network (let's call it the exposed port)
- 32801 on host network (let's call it the ephemeral port).
Browsers use address: externalHostname
:32801
Che server used address internalHostname
:32801 (before this PR)
After this PR Che server should use address containerIP
:4401
The difference is between ephemeral and exposed ports but also between internalHostname
and containerIP
:
internalHostname
= the hostname of the host where docker is running and in most cases is identical toexternalHostname
containerIP
= is the IP address of the workspace container, it's an IP of the docker0 network (you can get this IP address usingdocker inspect -f '{{ .NetworkSettings.IPAddress }}' <containerID>
).
Looking to the code of this PR it seems to me internalHostname
is still used by the Che server. containerIP
should be used instead.
String internalHostnameAndPort; | ||
if (useInternalAddress) { | ||
String internalPort = portProtocol.split("/")[0]; | ||
internalHostnameAndPort = internalHostname + ":" + internalPort; |
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.
@amisevsk there is a typo in variable externalHostame
. You haven't introduced that (that was probably me 😊) but it would be cool if you could fix it => externalHostname
if (useInternalAddress) { | ||
String containerHostName = null; | ||
if (networkSettings != null) { | ||
containerHostName = networkSettings.getIpAddress(); |
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 IP address the IP of the Che server container? What we need is instead the IP addresses of the workspace containers.
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 far as I can tell, the ContainerInfo
(which provides NetworkSettings
) injected into the LocalDockerInstanceRuntimeInfo
constructor is obtained from DockerConnector.inspectContainer()
, where the container inspect is the workspace container. See DockerInstance.java.
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.
@amisevsk you are right. Sorry about that :-)
Signed-off-by: Angel Misevski <[email protected]>
LGTM good job @amisevsk! |
@l0rd @amisevsk Honestly existing code is already over-complicated. And this PR makes what is happening here even less clear. I'll suggest a way how to refactor this code below. Please share your thoughts about this idea. Do you think that this approach is clearer and more extensible than current one? We can refactor this code in such a way:
... other things not related to this topic
HostPortEvaluationStrategyProvider (name just for example, feel free to use another name) uses property that defines chosen strategy impl and set of strategies impls and return needed HostPortEvaluationStrategy implementation. My description looks a bit complicated when I read it, but it is not really complex. So please ask me for an explanation if this algorithm is not clear enough to you. |
@garagatyi I like your approach and I agree that current code is overcomplicated. Just a couple of comments:
|
I mean it is useless in DockerInstanceRuntimeInfo constructor. Later I would want to refactor DockerInstance code again and remove DockerNode because it looks odd to me. I think we can move internal/external address setting into suggested HostPortEvaluationStrategy.
Do you suggest to merge this PR and make another one with refactoring or just open another one with the refactoring? If first then we will have to pass 2 QA cycles - for 1st and 2nd PRs. |
Ok I see what you mean. It makes sense. And we should move internalhost parameter to HostPortEvaluationStrategy too.
I guess that's not ideal because QA cycles are manual and take time. That's ok I will talk with @amisevsk to see how to handle this. |
@garagatyi I agree that the current setup is probably more complicated than it should be, and would be happy to do the refactor you suggest. For my understanding: Currently,
The result of (4) is what is returned by If I understand you correctly, you're suggesting that steps 2-4 are moved into the That makes sense to me. A few things I'm still not clear on though. It's maybe important to note that with the set up I'm running, the constructor for
Sorry for the novel, I want to make sure I understand. |
Yes, It is what I'm thinking about.
Yes, I suppose it should be a set of different strategies. They can be simple enough and don't need to override some default values. Apparently we will have some default strategy. I assume that we can set property with some strategy as a default and allow to override strategy with properties instead of changing assembly by binding needed strategy in Guice module. If vendor wants to implement new strategy it can contribute it into Che or bind it in customized assembly.
Yes, I believe we should get rid of LocalDockerInstanceRuntimeInfo |
I've opened a new pull request which includes the refactor suggested above: #3282 |
Closed; issue will be solved in #3282 |
What does this PR do?
Enables che-server to use the internal address of wsagent containers when it is running
in docker through setting an environment variable.
When
CHE_DOCKER_USE_INTERNAL_CONTAINER_ADDRESS
is set to "true", theServerProperties
objects returned as part ofDockerInstanceRuntimeInfo#getServers()
will use the internal address of the container as obtained from docker inspect.
If the environment variable is not set, then behaviour is unchanged.
This is a WIP so comments / suggestions are greatly appreciated.
What issues does this PR fix or reference?
Che server should connect to ws-agent on internal URL (#2030)
Previous behavior
Value used for internal address could cause issues contacting wsagent if firewall interfered.
New behavior
Che-server can now optionally communicate with wsagent directly.
PR type
Minor change checklist
Signed-off-by: Angel Misevski [email protected]