Skip to content

Commit 7c4d777

Browse files
committed
OCPBUGS-33787: Tolerate the absence of ingress capability on HyperShift clusters
- Implement alternative ingress fields from the console operator config API - Skip component route customizations if ingress capability is disabled - Use NodePort type for console and downloads services if ingress capability is disabled - Add document to describe how to implement alternative ingress on ROSA
1 parent d4bac3a commit 7c4d777

File tree

14 files changed

+442
-78
lines changed

14 files changed

+442
-78
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ docker build -t <registry>/<your-username>/console-operator:<version> .
178178
# following: docker.io/openshift/origin-console-operator:latest
179179
# for development, you are going to push to an alternate registry.
180180
# specifically it can look something like this:
181-
docker build -t quay.io/benjaminapetersen/console-operator:latest .
181+
docker build -f Dockerfile.rhel7 -t quay.io/benjaminapetersen/console-operator:latest .
182182
```
183183
You can optionally build a specific version.
184184

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# This 'console' service manifest is used when the ingress cluster capability is disabled.
2+
# Service will be exposed using a NodePort to enable the alternative ingress.
3+
apiVersion: v1
4+
kind: Service
5+
metadata:
6+
name: console
7+
namespace: openshift-console
8+
labels:
9+
app: console
10+
spec:
11+
ports:
12+
- name: https
13+
protocol: TCP
14+
port: 443
15+
targetPort: 8443
16+
selector:
17+
app: console
18+
component: ui
19+
type: NodePort
20+
sessionAffinity: None
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# This 'downloads' service manifest is used when the ingress cluster capability is disabled.
2+
# Service will be exposed using a NodePort to enable the alternative ingress.
3+
apiVersion: v1
4+
kind: Service
5+
metadata:
6+
namespace: openshift-console
7+
name: downloads
8+
spec:
9+
ports:
10+
- name: http
11+
port: 80
12+
protocol: TCP
13+
targetPort: 8080
14+
selector:
15+
app: console
16+
component: downloads
17+
type: NodePort
18+
sessionAffinity: None

docs/alb-ingress-rosa-hcp.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Use AWS ALB as alternative ingress on ROSA HCP
2+
3+
This doc aims at showing the effort needed to expose the OpenShift console via AWS ALB on a ROSA HCP cluster.
4+
The use case in mind is [HyperShift hosted clusters where the Ingress capability is disabled](https://github.com/openshift/enhancements/pull/1415).
5+
6+
## Requirements
7+
8+
- ROSA HCP OpenShift cluster.
9+
- [AWS Load Balancer Operator installed and its controller created](https://docs.openshift.com/rosa/networking/aws-load-balancer-operator.html).
10+
- User logged as a cluster admin.
11+
12+
## Procedure
13+
14+
### Create certificate in AWS Certificate Manager
15+
16+
In order to configure an HTTPS listener on AWS ALB you need to have a certificate created in AWS Certificate Manager.
17+
You can import an existing certificate or request a new one. Make sure the certificate is created in the same region as your cluster.
18+
Note the certificate ARN, you will need it later.
19+
20+
### Create Ingress resources for the NodePort services
21+
22+
To provision ALBs create the following resources:
23+
```bash
24+
cat <<EOF | oc apply -f -
25+
apiVersion: networking.k8s.io/v1
26+
kind: Ingress
27+
metadata:
28+
annotations:
29+
alb.ingress.kubernetes.io/scheme: internet-facing
30+
alb.ingress.kubernetes.io/target-type: instance
31+
alb.ingress.kubernetes.io/backend-protocol: HTTPS
32+
alb.ingress.kubernetes.io/certificate-arn: ${CERTIFICATE_ARN}
33+
name: console
34+
namespace: openshift-console
35+
spec:
36+
ingressClassName: alb
37+
rules:
38+
- http:
39+
paths:
40+
- path: /
41+
pathType: Prefix
42+
backend:
43+
service:
44+
name: console
45+
port:
46+
number: 443
47+
---
48+
apiVersion: networking.k8s.io/v1
49+
kind: Ingress
50+
metadata:
51+
annotations:
52+
alb.ingress.kubernetes.io/scheme: internet-facing
53+
alb.ingress.kubernetes.io/target-type: instance
54+
alb.ingress.kubernetes.io/backend-protocol: HTTP
55+
alb.ingress.kubernetes.io/certificate-arn: ${CERTIFICATE_ARN}
56+
name: downloads
57+
namespace: openshift-console
58+
spec:
59+
ingressClassName: alb
60+
rules:
61+
- http:
62+
paths:
63+
- path: /
64+
pathType: Prefix
65+
backend:
66+
service:
67+
name: downloads
68+
port:
69+
number: 80
70+
EOF
71+
```
72+
73+
### Update console config
74+
75+
Once the console ALBs are ready you need to let the console operator know which urls to use.
76+
Update the console operator config providing the custom urls:
77+
```bash
78+
$ CONSOLE_ALB_HOST=$(oc -n openshift-console get ing console -o yaml | yq .status.loadBalancer.ingress[0].hostname)
79+
$ DOWNLOADS_ALB_HOST=$(oc -n openshift-console get ing downloads -o yaml | yq .status.loadBalancer.ingress[0].hostname)
80+
$ oc patch console.operator.openshift.io cluster --type=merge -p "{\"spec\":{\"ingress\":{\"consoleURL\":\"https://${CONSOLE_ALB_HOST}\",\"clientDownloadsURL\":\"https://${DOWNLOADS_ALB_HOST}\"}}}"
81+
```
82+
83+
## Notes
84+
85+
1. ROSA HCP does not have the authentication operator, the authentication server is managed centrally by the HyperShift layer:
86+
```bash
87+
$ oc -n openshift-authentication-operator get deploy,route
88+
No resources found
89+
90+
$ oc -n openshift-authentication get pods,routes
91+
No resources found
92+
93+
$ oc get oauthclient | grep -v console
94+
NAME SECRET WWW-CHALLENGE TOKEN-MAX-AGE REDIRECT URIS
95+
openshift-browser-client false default https://oauth.mytestcluster.5199.s3.devshift.org:443/oauth/token/display
96+
openshift-challenging-client true default https://oauth.mytestcluster.5199.s3.devshift.org:443/oauth/token/implicit
97+
98+
$ oc -n openshift-console rsh deploy/console curl -k https://openshift.default.svc/.well-known/oauth-authorization-server
99+
{
100+
"issuer": "https://oauth.mytestcluster.5199.s3.devshift.org:443",
101+
"authorization_endpoint": "https://oauth.mytestcluster.5199.s3.devshift.org:443/oauth/authorize",
102+
"token_endpoint": "https://oauth.mytestcluster.5199.s3.devshift.org:443/oauth/token",
103+
```
104+
105+
2. When the ingress capability is disabled, the console operator relies on the end user to provide the console and download URLs (using the operator API) for health checks and oauthclient.
106+
107+
3. When the ingress capability is disabled, the console operator skips the implementation of the component route customization.
108+
109+
4. To simulate the absence of ingress connectivity when the ingress capability is disabled, set the desired replicas to zero in the default ingress controller:
110+
```bash
111+
$ oc -n openshift-ingress-operator patch ingresscontroller default --type='json' -p='[{"op": "replace", "path": "/spec/replicas", "value":0}]'
112+
```
113+
114+
## Links
115+
- [Demo of ALB ingress for the console on ROSA HCP](https://drive.google.com/file/d/1uWZgFbSeZTlDzlFyPW7QcH-625JsbSbw/view)

pkg/console/controllers/clidownloads/controller.go

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
// standard lib
55
"context"
66
"fmt"
7+
"net/url"
78
"time"
89

910
// kube
@@ -114,25 +115,37 @@ func (c *CLIDownloadsSyncController) Sync(ctx context.Context, controllerContext
114115
}
115116

116117
statusHandler := status.NewStatusHandler(c.operatorClient)
117-
ingressConfig, err := c.ingressConfigLister.Get(api.ConfigResourceName)
118-
if err != nil {
119-
return statusHandler.FlushAndReturn(err)
120-
}
121118

122-
activeRouteName := api.OpenShiftConsoleDownloadsRouteName
123-
routeConfig := routesub.NewRouteConfig(updatedOperatorConfig, ingressConfig, activeRouteName)
124-
if routeConfig.IsCustomHostnameSet() {
125-
activeRouteName = api.OpenshiftDownloadsCustomRouteName
126-
}
119+
var (
120+
downloadsURI *url.URL
121+
downloadsErr error
122+
)
123+
if len(operatorConfig.Spec.Ingress.ClientDownloadsURL) == 0 {
124+
ingressConfig, err := c.ingressConfigLister.Get(api.ConfigResourceName)
125+
if err != nil {
126+
return statusHandler.FlushAndReturn(err)
127+
}
127128

128-
downloadsRoute, downloadsRouteErr := c.routeLister.Routes(api.TargetNamespace).Get(activeRouteName)
129-
if downloadsRouteErr != nil {
130-
return downloadsRouteErr
131-
}
129+
activeRouteName := api.OpenShiftConsoleDownloadsRouteName
130+
routeConfig := routesub.NewRouteConfig(updatedOperatorConfig, ingressConfig, activeRouteName)
131+
if routeConfig.IsCustomHostnameSet() {
132+
activeRouteName = api.OpenshiftDownloadsCustomRouteName
133+
}
134+
135+
downloadsRoute, downloadsRouteErr := c.routeLister.Routes(api.TargetNamespace).Get(activeRouteName)
136+
if downloadsRouteErr != nil {
137+
return downloadsRouteErr
138+
}
132139

133-
downloadsURI, _, downloadsRouteErr := routeapihelpers.IngressURI(downloadsRoute, downloadsRoute.Spec.Host)
134-
if downloadsRouteErr != nil {
135-
return downloadsRouteErr
140+
downloadsURI, _, downloadsErr = routeapihelpers.IngressURI(downloadsRoute, downloadsRoute.Spec.Host)
141+
if downloadsErr != nil {
142+
return downloadsErr
143+
}
144+
} else {
145+
downloadsURI, downloadsErr = url.Parse(operatorConfig.Spec.Ingress.ClientDownloadsURL)
146+
if downloadsErr != nil {
147+
return fmt.Errorf("failed to parse downloads url: %w", downloadsErr)
148+
}
136149
}
137150

138151
ocConsoleCLIDownloads := PlatformBasedOCConsoleCLIDownloads(downloadsURI.String(), api.OCCLIDownloadsCustomResourceName)

pkg/console/controllers/healthcheck/controller.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"crypto/x509"
77
"fmt"
88
"net/http"
9+
"net/url"
910
"time"
1011

1112
// k8s
@@ -78,7 +79,6 @@ func NewHealthCheckController(
7879
util.IncludeNamesFilter(api.ConfigResourceName),
7980
operatorConfigInformer.Informer(),
8081
configV1Informers.Ingresses().Informer(),
81-
configV1Informers.Infrastructures().Informer(),
8282
).WithFilteredEventsInformers( // service
8383
util.IncludeNamesFilter(api.TrustedCAConfigMapName, api.OAuthServingCertConfigMapName),
8484
configMapInformer.Informer(),
@@ -153,10 +153,22 @@ func (c *HealthCheckController) CheckRouteHealth(ctx context.Context, operatorCo
153153
retry.DefaultRetry,
154154
func(err error) bool { return err != nil },
155155
func() error {
156-
url, _, err := routeapihelpers.IngressURI(route, route.Spec.Host)
157-
if err != nil {
158-
reason = "RouteNotAdmitted"
159-
return fmt.Errorf("console route is not admitted")
156+
var (
157+
url *url.URL
158+
err error
159+
)
160+
if len(operatorConfig.Spec.Ingress.ConsoleURL) == 0 {
161+
url, _, err = routeapihelpers.IngressURI(route, route.Spec.Host)
162+
if err != nil {
163+
reason = "RouteNotAdmitted"
164+
return fmt.Errorf("console route is not admitted")
165+
}
166+
} else {
167+
url, err = url.Parse(operatorConfig.Spec.Ingress.ConsoleURL)
168+
if err != nil {
169+
reason = "FailedParseConsoleURL"
170+
return fmt.Errorf("failed to parse console url: %w", err)
171+
}
160172
}
161173

162174
caPool, err := c.getCA(ctx, route.Spec.TLS)

pkg/console/controllers/oauthclients/oauthclients.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package oauthclients
33
import (
44
"context"
55
"fmt"
6+
"net/url"
67
"time"
78

89
corev1 "k8s.io/api/core/v1"
@@ -131,15 +132,26 @@ func (c *oauthClientsController) sync(ctx context.Context, controllerContext fac
131132
return err
132133
}
133134

134-
routeName := api.OpenShiftConsoleRouteName
135-
routeConfig := routesub.NewRouteConfig(operatorConfig, ingressConfig, routeName)
136-
if routeConfig.IsCustomHostnameSet() {
137-
routeName = api.OpenshiftConsoleCustomRouteName
138-
}
139-
140-
_, consoleURL, _, routeErr := routesub.GetActiveRouteInfo(c.routesLister, routeName)
141-
if routeErr != nil {
142-
return routeErr
135+
var consoleURL *url.URL
136+
137+
if len(operatorConfig.Spec.Ingress.ConsoleURL) == 0 {
138+
routeName := api.OpenShiftConsoleRouteName
139+
routeConfig := routesub.NewRouteConfig(operatorConfig, ingressConfig, routeName)
140+
if routeConfig.IsCustomHostnameSet() {
141+
routeName = api.OpenshiftConsoleCustomRouteName
142+
}
143+
144+
_, url, _, routeErr := routesub.GetActiveRouteInfo(c.routesLister, routeName)
145+
if routeErr != nil {
146+
return routeErr
147+
}
148+
consoleURL = url
149+
} else {
150+
url, err := url.Parse(operatorConfig.Spec.Ingress.ConsoleURL)
151+
if err != nil {
152+
return fmt.Errorf("failed to parse console url: %w", err)
153+
}
154+
consoleURL = url
143155
}
144156

145157
waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)

pkg/console/controllers/route/controller.go

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ type RouteSyncController struct {
4040
routeName string
4141
isHealthCheckEnabled bool
4242
// clients
43-
operatorClient v1helpers.OperatorClient
44-
routeClient routeclientv1.RoutesGetter
45-
operatorConfigLister operatorv1listers.ConsoleLister
46-
ingressConfigLister configlistersv1.IngressLister
47-
secretLister corev1listers.SecretLister
43+
operatorClient v1helpers.OperatorClient
44+
routeClient routeclientv1.RoutesGetter
45+
operatorConfigLister operatorv1listers.ConsoleLister
46+
ingressConfigLister configlistersv1.IngressLister
47+
secretLister corev1listers.SecretLister
48+
infrastructureConfigLister configlistersv1.InfrastructureLister
49+
clusterVersionLister configlistersv1.ClusterVersionLister
4850
}
4951

5052
func NewRouteSyncController(
@@ -63,13 +65,15 @@ func NewRouteSyncController(
6365
recorder events.Recorder,
6466
) factory.Controller {
6567
ctrl := &RouteSyncController{
66-
routeName: routeName,
67-
isHealthCheckEnabled: isHealthCheckEnabled,
68-
operatorClient: operatorClient,
69-
operatorConfigLister: operatorConfigInformer.Lister(),
70-
ingressConfigLister: configInformer.Config().V1().Ingresses().Lister(),
71-
routeClient: routev1Client,
72-
secretLister: secretInformer.Lister(),
68+
routeName: routeName,
69+
isHealthCheckEnabled: isHealthCheckEnabled,
70+
operatorClient: operatorClient,
71+
operatorConfigLister: operatorConfigInformer.Lister(),
72+
ingressConfigLister: configInformer.Config().V1().Ingresses().Lister(),
73+
routeClient: routev1Client,
74+
secretLister: secretInformer.Lister(),
75+
infrastructureConfigLister: configInformer.Config().V1().Infrastructures().Lister(),
76+
clusterVersionLister: configInformer.Config().V1().ClusterVersions().Lister(),
7377
}
7478

7579
configV1Informers := configInformer.Config().V1()
@@ -114,6 +118,35 @@ func (c *RouteSyncController) Sync(ctx context.Context, controllerContext factor
114118

115119
statusHandler := status.NewStatusHandler(c.operatorClient)
116120

121+
// Do not proceed to the route checks if alternative ingress is requested.
122+
switch c.routeName {
123+
case api.OpenShiftConsoleRouteName:
124+
if len(operatorConfig.Spec.Ingress.ConsoleURL) != 0 {
125+
return statusHandler.FlushAndReturn(nil)
126+
}
127+
case api.OpenShiftConsoleDownloadsRouteName:
128+
if len(operatorConfig.Spec.Ingress.ClientDownloadsURL) != 0 {
129+
return statusHandler.FlushAndReturn(nil)
130+
}
131+
}
132+
133+
infrastructureConfig, err := c.infrastructureConfigLister.Get(api.ConfigResourceName)
134+
if err != nil {
135+
return statusHandler.FlushAndReturn(err)
136+
}
137+
138+
clusterVersionConfig, err := c.clusterVersionLister.Get("version")
139+
if err != nil {
140+
return statusHandler.FlushAndReturn(err)
141+
}
142+
143+
// Disable the route check for external control plane topology (hypershift) if the ingress capability is disabled.
144+
// The components will miss the required RBAC to implement the custom hostname or TLS.
145+
// Link: https://github.com/openshift/enhancements/blob/f5290a98ea4f23f8e76621806b656a3849c74a17/enhancements/ingress/optional-ingress-hypershift.md#component-routes.
146+
if util.IsExternalControlPlaneWithIngressDisabled(infrastructureConfig, clusterVersionConfig) {
147+
return statusHandler.FlushAndReturn(nil)
148+
}
149+
117150
ingressConfig, err := c.ingressConfigLister.Get(api.ConfigResourceName)
118151
if err != nil {
119152
return statusHandler.FlushAndReturn(err)

0 commit comments

Comments
 (0)