CIS: OpenShift Operator + Service type LB
In my last post I installed the F5 CIS operator from the CLI and brought up the F5BigIpCtlr operand. This post picks up right after that: the operator is running, CIS is healthy, and now a customer wants to expose an application with a plain Service of type: LoadBalancer instead of an Ingress, a Route, or a VirtualServer custom resource.
Why Service Type LB?
Some teams don’t want to manage a CIS-specific CRD for every app - they just want the same type: LoadBalancer experience they’d get from a cloud provider’s native load balancer, and they want BIG-IP to be the thing that satisfies it on-prem.
The short version: CIS doesn’t watch Service objects for LoadBalancer behavior out of the box. Two operator-level args need to be set before CIS will even look at the Service, and then the Service itself needs an annotation telling CIS what IP to use.
Why a Service alone isn’t enough
If you just create a Service with type: LoadBalancer against a default CIS install, nothing happens. EXTERNAL-IP sits at <pending> forever, because there’s no cloud controller manager assigning it and CIS, by default, isn’t watching for this resource type at all.
CIS has to be put into a mode where it watches Custom Resources, and on OpenShift, it needs to know which CNI plugin is in play so it can correctly read pod and node networking information. Both of these are operator-level settings, not something you can fix from the Service manifest alone.
Notable parameters
Notice these parameters were all included when we defined our operand in our last post.
| Parameter | What it does |
|---|---|
custom_resource_mode |
Switches CIS into CRD mode. This is also the mode that gives CIS native support for Service objects of type: LoadBalancer - without it, CIS only processes Routes or ConfigMaps, never Services. |
orchestration_cni |
Tells CIS which CNI plugin the cluster runs, so it can correctly read pod CIDR and node IP information for that CNI. For an OpenShift cluster running OVN-Kubernetes (the default since 4.x), this is ovn-k8s. |
static_routing_mode |
Adds Static Routes on the BIGIP so that traffic can be directly route to the pods. (Without tunnels) |
Worth calling out: custom_resource_mode is the parameter that’s actually mandatory for Service type LoadBalancer support. static_routing_mode and orchestration_cni enable routes to be created on F5 BIG-IP so that the BIG-IP can know which Pod IP address blocks belong to which OpenShift nodes.
The F5BigIpCtlr operand
This is the same operand I had in the last blog post.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: cis.f5.com/v1
kind: F5BigIpCtlr
metadata:
name: cis1
namespace: kube-system
spec:
namespace: kube-system
args:
bigip_url: 10.0.4.11
bigip_partition: openshift
log_level: DEBUG
insecure: true
pool_member_type: cluster
manage_routes: false
custom_resource_mode: true
static_routing_mode: true
orchestration_cni: ovn-k8s
bigip_login_secret: bigip-login
image:
repo: f5networks/cntr-ingress-svcs
user: registry.connect.redhat.com
pullPolicy: Always
version: 2.20.4-ubi10
ingressClass:
create: false
defaultController: false
ingressClassName: f5
rbac:
create: true
namespaced: false
serviceAccount:
create: true
Exposing the application
With CIS configured, the customer’s application Service just needs type: LoadBalancer plus one CIS-specific annotation that tells CIS which IP to configure on BIG-IP for the virtual server.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Service
metadata:
annotations:
cis.f5.com/ip: 10.8.3.1 #the VIP gets created with this IP
labels:
app: hello-world
name: hello-world-svc-1
namespace: hello-world
spec:
ports:
- name: port8080
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: hello-world
type: LoadBalancer
Check that the IP landed on the Service:
1
oc get svc hello-world-svc-1 -n hello-world
EXTERNAL-IP should now show 10.8.3.1 instead of <pending>, and CIS will have created a corresponding LTM virtual server on BIG-IP at that address.
A note on the IPAM alternative
cis.f5.com/ip is the hardcoded path - good for a customer who already owns a static IP and just wants CIS to honor it, which is the case I’m documenting here. F5 also ships an F5 IPAM Controller that can assign the address automatically from a pre-defined range, using a cis.f5.com/ipamLabel annotation instead of cis.f5.com/ip. That path needs the additional ipam: true arg on the CIS operand and an F5IPAM resource to define the range.
If both annotations happen to be present on the same Service, cis.f5.com/ip wins - CIS treats the hardcoded IP as the more specific instruction. For most customer environments I’ve seen, where IP assignment is already governed by an IPAM process outside the cluster, hardcoding via cis.f5.com/ip is the simpler and more predictable choice. IPAM earns its keep once you have many Services and don’t want to hand-manage individual addresses.
Final thoughts
Simple blog post intended for a copy/paste option for a customer that has installed CIS via OpenShift operator and wants to expose services of type LoadBalancer.
If you’re working through this for a real customer deployment, feel free to reach out!