Skip to content
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

Gh17060 gateway single host kube #17557

Merged
merged 50 commits into from
Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
a2bb8c9
first treafik config generator implementation
sparkoo Aug 4, 2020
56a3f8e
refactor generating gateway config
sparkoo Aug 4, 2020
1417611
fix TraefikGatewayConfigGeneratorTest
sparkoo Aug 4, 2020
5d3e625
test GatewayServerExposer
sparkoo Aug 4, 2020
9e275c4
nullsafer way how to get port
sparkoo Aug 4, 2020
3cfabf4
create route configmaps in che namespace
sparkoo Aug 6, 2020
e0ec48c
create gateway configmaps in che namespace
sparkoo Aug 12, 2020
641d6ae
gateway prefix path without slash
sparkoo Aug 12, 2020
eb28dce
Merge branch 'master' into gh17060-gatewaySingleHostKube
sparkoo Aug 12, 2020
2fcd61d
cleanup
sparkoo Aug 12, 2020
b9de188
rename GatewayRouterProvisioner to Resolver and create configmaps in …
sparkoo Aug 12, 2020
6e599ec
minor fixes
sparkoo Aug 12, 2020
cc9ed29
format
sparkoo Aug 12, 2020
cf56415
license headers
sparkoo Aug 12, 2020
7b8497b
fix GatewayTlsProvisionerTest
sparkoo Aug 13, 2020
3fc2524
test GatewayRouterResolver
sparkoo Aug 13, 2020
067b3b0
test cheNamespace cleanup
sparkoo Aug 13, 2020
97e3de3
test creating configmaps from gateway configs in KubernetesInternalRu…
sparkoo Aug 13, 2020
de5520c
CheNamespace and CheInstallationLocation tests, refactors
sparkoo Aug 13, 2020
31e8fa0
format, license headers, javadocs,
sparkoo Aug 13, 2020
4035eb3
test generating traefik configuration
sparkoo Aug 13, 2020
f42a4b2
javadocs GatewayRouteConfig
sparkoo Aug 13, 2020
ad2b416
Merge branch 'master' into gh17060-gatewaySingleHostKube
sparkoo Aug 13, 2020
1e163fb
javadocs
sparkoo Aug 13, 2020
a563b34
fix CheInstallationLocation test
sparkoo Aug 17, 2020
721495f
resolve servers only for configmaps that has machinename annotation
sparkoo Aug 17, 2020
62e2314
add X-Forwarded-Proto header to traefik config
sparkoo Aug 18, 2020
66b9d3e
refactor so KubernetesInternalRuntime only creates configmaps
sparkoo Aug 18, 2020
3cd2006
get rid of GatewayRouteConfig
sparkoo Aug 19, 2020
9508e6c
javadocs and refactorings
sparkoo Aug 20, 2020
5e6051d
revert unnecessary change
sparkoo Aug 20, 2020
e30d279
cleanup, refactor, javadocs of GatewayRouteProvisioner
sparkoo Aug 20, 2020
8480ba9
cleanup, better exception messages
sparkoo Aug 20, 2020
183d58c
javadocs and minor refactorings
sparkoo Aug 20, 2020
8d796ae
javadocs and minor refactors
sparkoo Aug 20, 2020
d5cc155
remove singlehost from openshift for now
sparkoo Aug 20, 2020
a82b883
verify that gatewayRouterProvisioner is called
sparkoo Aug 20, 2020
60b626a
fix test checking proper configmaps are created in che namespace
sparkoo Aug 20, 2020
e4db204
CheNamespace checks configmaps for proper annotations + fix and add t…
sparkoo Aug 20, 2020
49375ae
test GatewayRouterProvisioner
sparkoo Aug 20, 2020
69a03fb
test GatewayTlcProvisioner
sparkoo Aug 20, 2020
28c8fbe
test GatewayServerExposer
sparkoo Aug 20, 2020
fe3a96c
test Traefik config generator
sparkoo Aug 20, 2020
ee77b6b
revert unnecessary change
sparkoo Aug 20, 2020
83e4774
license headers
sparkoo Aug 20, 2020
c77a4ee
fix NPE when checking object to create in che installation namespace
sparkoo Aug 20, 2020
8f1c14e
javadocs
sparkoo Aug 20, 2020
4d6337c
rename CheKubernetesClientFactory -> CheServerKubernetesClientFactory…
sparkoo Aug 21, 2020
ec1201d
expose paths with ending slash
sparkoo Aug 21, 2020
fe6e99e
Merge branch 'master' into gh17060-gatewaySingleHostKube
sparkoo Aug 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
import org.eclipse.che.security.oauth.EmbeddedOAuthAPI;
import org.eclipse.che.security.oauth.OAuthAPI;
import org.eclipse.che.security.oauth.OpenShiftOAuthModule;
import org.eclipse.che.workspace.infrastructure.kubernetes.GatewayRouterProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfraModule;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructure;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.eclipse.che.workspace.infrastructure.kubernetes;

import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner.getSecureProtocol;

import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.GatewayRouteConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.provision.InternalEnvironmentProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayRouteConfigGenerator;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.TraefikGatewayRouteConfigGenerator;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;

@Singleton
public class GatewayRouterProvisioner {

protected static final Map<String, String> GATEWAY_CONFIGMAP_LABELS =
ImmutableMap.<String, String>builder()
.put("app", "che")
.put("role", "gateway-config")
.build();

private final KubernetesClientFactory clientFactory;
private final Executor k8sExecutor;

@Inject
public GatewayRouterProvisioner(KubernetesClientFactory clientFactory,
KubernetesSharedPool sharedPool) {
this.clientFactory = clientFactory;
this.k8sExecutor = sharedPool.getExecutor();
}

public List<ConfigMap> provision(RuntimeIdentity id, InternalEnvironment internalEnvironment)
throws InfrastructureException {
if (internalEnvironment.getGatewayRouteConfigs().isEmpty()) {
return Collections.emptyList();
}
List<ConfigMap> routeConfigMaps = new ArrayList<>();
KubernetesNamespace cheNamespace = new KubernetesNamespace(clientFactory, k8sExecutor, "che",
sparkoo marked this conversation as resolved.
Show resolved Hide resolved
id.getWorkspaceId());

for (GatewayRouteConfig routeConfig : internalEnvironment.getGatewayRouteConfigs()) {
GatewayRouteConfigGenerator gatewayRouteConfigGenerator = new TraefikGatewayRouteConfigGenerator(
id.getInfrastructureNamespace());
gatewayRouteConfigGenerator.addRouteConfig(routeConfig);
ConfigMapBuilder configMapBuilder = new ConfigMapBuilder()
.withNewMetadata()
.withName(id.getWorkspaceId() + routeConfig.getName())
.withLabels(GATEWAY_CONFIGMAP_LABELS)
.withAnnotations(routeConfig.getAnnotations())
.endMetadata()
.withData(gatewayRouteConfigGenerator.generate());

ConfigMap routeConfigMap = cheNamespace.configMaps().create(configMapBuilder.build());
Copy link
Contributor

Choose a reason for hiding this comment

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

I afraid you will face the permission problem accessing che namespace, which i have faced during search of CA configmap.

Copy link
Member Author

@sparkoo sparkoo Aug 6, 2020

Choose a reason for hiding this comment

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

good point. I'm afraid we would have to somehow create that configmap in namespace with gateway pod. If we won't have permissions for that, we would have to add it. I'll look what permissions we have and if we need more. Alternatively, we could configure configbump to watch configmaps on all namespaces, or namespaces with running workspaces, which would require higher permissions on configbump SA.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be a big problem to require the che SA to be able to crud configmaps in the che namespace?

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 works with current helm install (we use cluster-admin). With operator, we currently can't use different namespaces anyway because lack of permissions and in one namespace it works fine.

So last option is OpenShift OAuth. I didn't try it, but this task is about Kubernetes so I would leave this potential permissions issue to #17061

routeConfigMaps.add(routeConfigMap);
}

return routeConfigMaps;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,13 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayRouteConfigGenerator;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServiceExposureStrategyProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.TraefikGatewayRouteConfigGenerator;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactoryProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ public class KubernetesInternalRuntime<E extends KubernetesEnvironment>
private final PreviewUrlCommandProvisioner previewUrlCommandProvisioner;
private final SecretAsContainerResourceProvisioner secretAsContainerResourceProvisioner;
private final KubernetesServerResolverFactory serverResolverFactory;
private final GatewayRouterProvisioner gatewayRouterProvisioner;
protected final Tracer tracer;

@Inject
Expand All @@ -155,6 +156,7 @@ public KubernetesInternalRuntime(
PreviewUrlCommandProvisioner previewUrlCommandProvisioner,
SecretAsContainerResourceProvisioner secretAsContainerResourceProvisioner,
KubernetesServerResolverFactory kubernetesServerResolverFactory,
GatewayRouterProvisioner gatewayRouterProvisioner,
Tracer tracer,
@Assisted KubernetesRuntimeContext<E> context,
@Assisted KubernetesNamespace namespace) {
Expand All @@ -180,6 +182,7 @@ public KubernetesInternalRuntime(
this.secretAsContainerResourceProvisioner = secretAsContainerResourceProvisioner;
this.serverResolverFactory = kubernetesServerResolverFactory;
this.tracer = tracer;
this.gatewayRouterProvisioner = gatewayRouterProvisioner;
}

@Override
Expand Down Expand Up @@ -615,6 +618,7 @@ protected void startMachines() throws InfrastructureException {

createSecrets(k8sEnv, workspaceId);
List<ConfigMap> createdConfigMaps = createConfigMaps(k8sEnv, workspaceId);
createdConfigMaps.addAll(gatewayRouterProvisioner.provision(getContext().getIdentity(), k8sEnv));
List<Service> createdServices = createServices(k8sEnv, workspaceId);

// needed for resolution later on, even though n routes are actually created by ingress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ public KubernetesRuntimeContext(
KubernetesRuntimeStateCache runtimeStatuses,
@Assisted T kubernetesEnvironment,
@Assisted RuntimeIdentity identity,
@Assisted RuntimeInfrastructure infrastructure)
throws ValidationException, InfrastructureException {
@Assisted RuntimeInfrastructure infrastructure) {
super(kubernetesEnvironment, identity, infrastructure);
this.namespaceFactory = namespaceFactory;
this.runtimeFactory = runtimeFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model.Config;

/**
* Defines an internal API for managing {@link ConfigMap} instances in {@link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import javax.inject.Singleton;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl;
import org.eclipse.che.api.workspace.server.spi.environment.GatewayRouteConfig;
import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
Expand All @@ -45,14 +46,14 @@ public void provision(T k8sEnv, RuntimeIdentity identity)
return;
}

for (ConfigMap cm : k8sEnv.getConfigMaps().values()) {
useSecureProtocolForGatewayServers(cm);
for (GatewayRouteConfig routeConfig : k8sEnv.getGatewayRouteConfigs()) {
useSecureProtocolForGatewayServers(routeConfig);
}
}

private void useSecureProtocolForGatewayServers(ConfigMap cm) {
private void useSecureProtocolForGatewayServers(GatewayRouteConfig routeConfig) {
Map<String, ServerConfigImpl> servers =
Annotations.newDeserializer(cm.getMetadata().getAnnotations()).servers();
Annotations.newDeserializer(routeConfig.getAnnotations()).servers();

if (servers.isEmpty()) {
return;
Expand All @@ -61,8 +62,8 @@ private void useSecureProtocolForGatewayServers(ConfigMap cm) {
servers.values().forEach(s -> s.setProtocol(getSecureProtocol(s.getProtocol())));

Map<String, String> annotations = Annotations.newSerializer().servers(servers).annotations();
if (!annotations.isEmpty() && cm.getMetadata().getAnnotations() != null) {
cm.getMetadata().getAnnotations().putAll(annotations);
if (!annotations.isEmpty() && routeConfig.getAnnotations() != null) {
routeConfig.getAnnotations().putAll(annotations);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.external;

import java.util.Map;
import org.eclipse.che.api.workspace.server.spi.environment.GatewayRouteConfig;

/**
* Generates config for single external server that we want to expose in the Gateway.
*
* <p>Implementation provides configuration for specific Gateway technology (e.g., Traefik).
*/
public interface GatewayRouteConfigGenerator {
void addRouteConfig(GatewayRouteConfig routeConfig);

/**
* Generates content of configuration for service, defined by passed parameters, that should be
* exposed by the Gateway. Returned {@code Map<String, String>} will be used as a value of
* ConfigMap.
*
* <p>Implementation must ensure that Gateway configured with returned content will route the
* requests on {@code path} into {@code serviceUrl}. Also it must strip {@code path} from request
* url.
*
* @param name name of the service
* @param serviceUrl url of service we want to route to
* @param path path to route and strip
* @return full content of configuration for the service
*/
Map<String, String> generate();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,102 @@
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.external;

import io.fabric8.kubernetes.api.model.IntOrString;
import io.fabric8.kubernetes.api.model.ServicePort;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl;
import org.eclipse.che.api.workspace.server.spi.environment.GatewayRouteConfig;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer;

/**
* Uses Traefik gateway configured with ConfigMaps to expose servers.
*
* <p>TODO: implement
* Uses gateway configured with ConfigMaps to expose servers.
*
* @param <T> type of environment
*/
public class GatewayServerExposer<T extends KubernetesEnvironment>
sparkoo marked this conversation as resolved.
Show resolved Hide resolved
implements ExternalServerExposer<T> {

private final ExternalServiceExposureStrategy strategy;

@Inject
public GatewayServerExposer(
ExternalServiceExposureStrategy strategy) {
this.strategy = strategy;
}

/**
* Exposes service port on given service externally (outside kubernetes cluster) using the Gateway
* specific configurations.
*
* @param k8sEnv Kubernetes environment
* @param machineName machine containing servers
* @param serviceName service associated with machine, mapping all machine server ports
* @param serverId non-null for a unique server, null for a compound set of servers that should be
* exposed together.
* @param servicePort specific service port to be exposed externally
* @param externalServers server configs of servers to be exposed externally
*/
@Override
public void expose(
T k8sEnv,
String machineName,
@Nullable String machineName,
String serviceName,
String serverId,
ServicePort servicePort,
Map<String, ServerConfig> externalServers) {
throw new UnsupportedOperationException("Not implemented yet.");

if (serverId == null) {
// this is the ID for non-unique servers
serverId = servicePort.getName();
}

k8sEnv.addGatewayRouteConfig(
createGatewayRouteConfig(machineName, serviceName, serverId, servicePort, externalServers));
}

private GatewayRouteConfig createGatewayRouteConfig(String machineName,
String serviceName,
String serverId,
ServicePort servicePort,
Map<String, ServerConfig> serversConfigs) {
final String serverName = KubernetesServerExposer.makeServerNameValidForDns(serverId);
final String name = createName(serviceName, serverName);
final String path = ensureEndsWithSlash(strategy.getExternalPath(serviceName, serverName));
final Map<String, String> annotations = createAnnotations(serversConfigs, path, machineName);
return new GatewayRouteConfig(name, serviceName, getTargetPort(servicePort.getTargetPort()),
path, annotations);
}

private String ensureEndsWithSlash(String path) {
return path.endsWith("/") ? path : path + '/';
}

private String createName(String serviceName, String serverName) {
return serviceName + "-" + serverName;
}

private String getTargetPort(IntOrString targetPort) {
return targetPort.getIntVal() != null
? targetPort.getIntVal().toString()
: targetPort.getStrVal();
}

private Map<String, String> createAnnotations(
Map<String, ServerConfig> serversConfigs, String path, String machineName) {
Map<String, ServerConfig> configsWithPaths = new HashMap<>();
for (String scKey : serversConfigs.keySet()) {
configsWithPaths.put(scKey, new ServerConfigImpl(serversConfigs.get(scKey)).withPath(path));
sparkoo marked this conversation as resolved.
Show resolved Hide resolved
}

return Annotations.newSerializer()
.servers(configsWithPaths)
.machineName(machineName)
.annotations();
}
}
Loading