Using the Default HAProxy Router

Overview

The oc adm router command is provided with the administrator CLI to simplify the tasks of setting up routers in a new installation. The oc adm router command creates the service and deployment configuration objects. Use the --service-account option to specify the service account the router will use to contact the master.

The router service account can be created in advance or created by the oc adm router --service-account command.

Every form of communication between OKD components is secured by TLS and uses various certificates and authentication methods. The --default-certificate .pem format file can be supplied or one is created by the oc adm router command. When routes are created, the user can provide route certificates that the router will use when handling the route.

When deleting a router, ensure the deployment configuration, service, and secret are deleted as well.

Routers are deployed on specific nodes. This makes it easier for the cluster administrator and external network manager to coordinate which IP address will run a router and which traffic the router will handle. The routers are deployed on specific nodes by using node selectors.

Routers use host networking by default, and they directly attach to port 80 and 443 on all interfaces on a host. Restrict routers to hosts where ports 80/443 are available and not being consumed by another service, and set this using node selectors and the scheduler configuration. As an example, you can achieve this by dedicating infrastructure nodes to run services such as routers.

It is recommended to use separate distinct openshift-router service account with your router. This can be provided using the —service-account flag to the oc adm router command.

  1. $ oc adm router dry-run service-account=router (1)
1—service-account is the name of a service account for the openshift-router. It is recommended using an openshift-router specific service account with appropriate permissions.

Router pods created using oc adm router have default resource requests that a node must satisfy for the router pod to be deployed. In an effort to increase the reliability of infrastructure components, the default resource requests are used to increase the QoS tier of the router pods above pods without resource requests. The default values represent the observed minimum resources required for a basic router to be deployed and can be edited in the routers deployment configuration and you may want to increase them based on the load of the router.

Creating a Router

If the router does not exist, run the following to create a router:

  1. $ oc adm router <router_name> --replicas=<number> --service-account=router --extended-logging=true

--replicas is usually 1 unless a high availability configuration is being created.

--extended-logging=true configures the router to forward logs that are generated by HAProxy to the syslog container.

To find the host IP address of the router:

  1. $ oc get po <router-pod> --template={{.status.hostIP}}

You can also use router shards to ensure that the router is filtered to specific namespaces or routes, or set any environment variables after router creation. In this case create a router for each shard.

Other Basic Router Commands

Checking the Default Router

First, ensure you have created the router service account before deploying a router.

To check if a default router, named router, already exists:

  1. $ oc adm router --dry-run --service-account=router

Viewing the Default Router

To see what the default router would look like if created:

  1. $ oc adm router --dry-run -o yaml --service-account=router

Configuring the Router to Forward HAProxy Logs

You can configure the router to forward logs that are generated by HAProxy to an rsyslog sidecar container. The --extended-logging=true parameter appends the syslog container to forward HAProxy logs to standard output.

  1. $ oc adm router --extended-logging=true

The following example is the configuration for a router that uses --extended-logging=true:

  1. $ oc get pod router-1-xhdb9 -o yaml
  2. apiVersion: v1
  3. kind: Pod
  4. spec:
  5. containers:
  6. - env:
  7. ....
  8. - name: ROUTER_SYSLOG_ADDRESS (1)
  9. value: /var/lib/rsyslog/rsyslog.sock
  10. ....
  11. - command: (2)
  12. - /sbin/rsyslogd
  13. - -n
  14. - -i
  15. - /tmp/rsyslog.pid
  16. - -f
  17. - /etc/rsyslog/rsyslog.conf
  18. image: registry.redhat.io/openshift3/ose-haproxy-router:v3.11.188
  19. imagePullPolicy: IfNotPresent
  20. name: syslog
1The —extended-logging=true parameter creates a socket file for the logs.
2The —extended-logging=true parameter adds a container to the router. In the container, the rsyslog process is running as: /sbin/rsyslogd -n -i /tmp/rsyslog.pid -f /etc/rsyslog/rsyslog.conf.

Use the following commands to view the HAProxy logs:

  1. $ oc set env dc/test-router ROUTER_LOG_LEVEL=info (1)
  2. $ oc logs -f <pod-name> -c syslog (2)
1Set the log level to info or debug. The default is warning.
2Specify the name of the router pod to view the logs.

The HAProxy logs take the following form:

  1. 2020-04-14T03:05:36.629527+00:00 test-311-node-1 haproxy[43]: 10.0.151.166:59594 [14/Apr/2020:03:05:36.627] fe_no_sni~ be_secure:openshift-console:console/pod:console-b475748cb-t6qkq:console:10.128.0.5:8443 0/0/1/1/2 200 393 - - --NI 2/1/0/1/0 0/0 "HEAD / HTTP/1.1"
  2. 2020-04-14T03:05:36.633024+00:00 test-311-node-1 haproxy[43]: 10.0.151.166:59594 [14/Apr/2020:03:05:36.528] public_ssl be_no_sni/fe_no_sni 95/1/104 2793 -- 1/1/0/0/0 0/0

Deploying the Router to a Labeled Node

To deploy the router to any node(s) that match a specified node label:

  1. $ oc adm router <router_name> --replicas=<number> --selector=<label> \
  2. --service-account=router

For example, if you want to create a router named router and have it placed on a node labeled with node-role.kubernetes.io/infra=true:

  1. $ oc adm router router --replicas=1 --selector='node-role.kubernetes.io/infra=true' \
  2. --service-account=router

During cluster installation, the openshift_router_selector and openshift_registry_selector Ansible settings are set to node-role.kubernetes.io/infra=true by default. The default router and registry will only be automatically deployed if a node exists that matches the node-role.kubernetes.io/infra=true label.

For information on updating labels, see Updating Labels on Nodes.

Multiple instances are created on different hosts according to the scheduler policy.

Using a Different Router Image

To use a different router image and view the router configuration that would be used:

  1. $ oc adm router <router_name> -o <format> --images=<image> \
  2. --service-account=router

For example:

  1. $ oc adm router region-west -o yaml --images=myrepo/somerouter:mytag \
  2. --service-account=router

Filtering Routes to Specific Routers

Using the ROUTE_LABELS environment variable, you can filter routes so that they are used only by specific routers.

For example, if you have multiple routers, and 100 routes, you can attach labels to the routes so that a portion of them are handled by one router, whereas the rest are handled by another.

  1. After creating a router, use the ROUTE_LABELS environment variable to tag the router:

    1. $ oc set env dc/<router=name> ROUTE_LABELS="key=value"
  2. Add the label to the desired routes:

    1. oc label route <route=name> key=value
  3. To verify that the label has been attached to the route, check the route configuration:

    1. $ oc describe route/<route_name>

Setting the Maximum Number of Concurrent Connections

The router can handle a maximum number of 20000 connections by default. You can change that limit depending on your needs. Having too few connections prevents the health check from working, which causes unnecessary restarts. You need to configure the system to support the maximum number of connections. The limits shown in 'sysctl fs.nr_open' and 'sysctl fs.file-max' must be large enough. Otherwise, HAproxy will not start.

When the router is created, the --max-connections= option sets the desired limit:

  1. $ oc adm router --max-connections=10000 ....

Edit the ROUTER_MAX_CONNECTIONS environment variable in the router’s deployment configuration to change the value. The router pods are restarted with the new value. If ROUTER_MAX_CONNECTIONS is not present, the default value of 20000, is used.

A connection includes the frontend and internal backend. This counts as two connections. Be sure to set ROUTER_MAX_CONNECTIONS to double than the number of connections you intend to create.

HAProxy Strict SNI

The HAProxy strict-sni can be controlled through the ROUTER_STRICT_SNI environment variable in the router’s deployment configuration. It can also be set when the router is created by using the --strict-sni command line option.

  1. $ oc adm router --strict-sni

TLS Cipher Suites

Set the router cipher suite using the --ciphers option when creating a router:

  1. $ oc adm router --ciphers=modern ....

The values are: modern, intermediate, or old, with intermediate as the default. Alternatively, a set of “:” separated ciphers can be provided. The ciphers must be from the set displayed by:

  1. $ openssl ciphers

Alternatively, use the ROUTER_CIPHERS environment variable for an existing router.

Mutual TLS Authentication

Client access to the router and the backend services can be restricted using mutual TLS authentication. The router will reject requests from clients not in its authenticated set. Mutual TLS authentication is implemented on client certificates and can be controlled based on the certifying authorities (CAs) issuing the certificates, the certificate revocation list and/or any certificate subject filters. Use the mutual tls config options --mutual-tls-auth, --mutual-tls-auth-ca, --mutual-tls-auth-crl and --mutual-tls-auth-filter when creating a router:

  1. $ oc adm router --mutual-tls-auth=required \
  2. --mutual-tls-auth-ca=/local/path/to/cacerts.pem ....

The --mutual-tls-auth values are required, optional, or none, with none as the default. The --mutual-tls-auth-ca value specifies a file containing one or more CA certificates. These CA certificates are used by the router to verify a client’s certificate.

The --mutual-tls-auth-crl can be used specify the certificate revocation list to handle cases where certificates (issued by valid certifying authorities) have been revoked.

  1. $ oc adm router --mutual-tls-auth=required \
  2. --mutual-tls-auth-ca=/local/path/to/cacerts.pem \
  3. --mutual-tls-auth-filter='^/CN=my.org/ST=CA/C=US/O=Security/OU=OSE$' \
  4. ....

The --mutual-tls-auth-filter value can be used for fine grain access control based on the certificate subject. The value is a regular expression, which is to used to match up the certificate’s subject.

The mutual TLS authentication filter example above shows you a restrictive regular expression (regex) — anchored with ^ and $ — that exactly matches a certificate subject. If you decide to use a less restrictive regular expression, please be aware that this can potentially match certificates issued by any CAs you have deemed to be valid. It is recommended to also use the —mutual-tls-auth-ca option so that you have finer control over the issued certificates.

Using --mutual-tls-auth=required ensures that you only allow authenticated clients access to the backend resources. This means that the client is always required to provide authentication information (aka a client certificate). To make the mutual TLS authentication optional, use --mutual-tls-auth=optional (or use none to disable it - this is the default). Note here that optional means that you do not require a client to present any authentication information and if the client provides any authentication information, that is just passed on to the backend in the X-SSL* HTTP headers.

  1. $ oc adm router --mutual-tls-auth=optional \
  2. --mutual-tls-auth-ca=/local/path/to/cacerts.pem \
  3. ....

When mutual TLS authentication support is enabled (either using the required or optional value for the --mutual-tls-auth flag), the client authentication information is passed to the backend in the form of X-SSL* HTTP headers.

Examples of the X-SSL* HTTP headers X-SSL-Client-DN: the full distinguished name (DN) of the certificate subject. X-SSL-Client-NotBefore: the client certificate start date in YYMMDDhhmmss[Z] format. X-SSL-Client-NotAfter: the client certificate end date in YYMMDDhhmmss[Z] format. X-SSL-Client-SHA1: the SHA-1 fingerprint of the client certificate. X-SSL-Client-DER: provides full access to the client certificate. Contains the DER formatted client certificate encoded in base-64 format.

Highly-Available Routers

You can set up a highly-available router on your OKD cluster using IP failover. This setup has multiple replicas on different nodes so the failover software can switch to another replica if the current one fails.

Customizing the Router Service Ports

You can customize the service ports that a template router binds to by setting the environment variables **ROUTER_SERVICE_HTTP_PORT** and **ROUTER_SERVICE_HTTPS_PORT**. This can be done by creating a template router, then editing its deployment configuration.

The following example creates a router deployment with 0 replicas and customizes the router service HTTP and HTTPS ports, then scales it appropriately (to 1 replica).

  1. $ oc adm router --replicas=0 --ports='10080:10080,10443:10443' (1)
  2. $ oc set env dc/router ROUTER_SERVICE_HTTP_PORT=10080 \
  3. ROUTER_SERVICE_HTTPS_PORT=10443
  4. $ oc scale dc/router --replicas=1
1Ensures exposed ports are appropriately set for routers that use the container networking mode —host-network=false.

If you do customize the template router service ports, you will also need to ensure that the nodes where the router pods run have those custom ports opened in the firewall (either via Ansible or iptables, or any other custom method that you use via firewall-cmd).

The following is an example using iptables to open the custom router service ports.

  1. $ iptables -A OS_FIREWALL_ALLOW -p tcp --dport 10080 -j ACCEPT
  2. $ iptables -A OS_FIREWALL_ALLOW -p tcp --dport 10443 -j ACCEPT

Working With Multiple Routers

An administrator can create multiple routers with the same definition to serve the same set of routes. Each router will be on a different node and will have a different IP address. The network administrator will need to get the desired traffic to each node.

Multiple routers can be grouped to distribute routing load in the cluster and separate tenants to different routers or shards. Each router or shard in the group admits routes based on the selectors in the router. An administrator can create shards over the whole cluster using ROUTE_LABELS. A user can create shards over a namespace (project) by using NAMESPACE_LABELS.

Adding a Node Selector to a Deployment Configuration

Making specific routers deploy on specific nodes requires two steps:

  1. Add a label to the desired node:

    1. $ oc label node 10.254.254.28 "router=first"
  2. Add a node selector to the router deployment configuration:

    1. $ oc edit dc <deploymentConfigName>

    Add the template.spec.nodeSelector field with a key and value corresponding to the label:

    1. ...
    2. template:
    3. metadata:
    4. creationTimestamp: null
    5. labels:
    6. router: router1
    7. spec:
    8. nodeSelector: (1)
    9. router: "first"
    10. ...
    1The key and value are router and first, respectively, corresponding to the router=first label.

Using Router Shards

Router sharding uses NAMESPACE_LABELS and ROUTE_LABELS, to filter router namespaces and routes. This enables you to distribute subsets of routes over multiple router deployments. By using non-overlapping subsets, you can effectively partition the set of routes. Alternatively, you can define shards comprising overlapping subsets of routes.

By default, a router selects all routes from all projects (namespaces). Sharding involves adding labels to routes or namespaces and label selectors to routers. Each router shard comprises the routes that are selected by a specific set of label selectors or belong to the namespaces that are selected by a specific set of label selectors.

The router service account must have the [cluster reader] permission set to allow access to labels in other namespaces.

Router Sharding and DNS

Because an external DNS server is needed to route requests to the desired shard, the administrator is responsible for making a separate DNS entry for each router in a project. A router will not forward unknown routes to another router.

Consider the following example:

  • Router A lives on host 192.168.0.5 and has routes with *.foo.com.

  • Router B lives on host 192.168.1.9 and has routes with *.example.com.

Separate DNS entries must resolve *.foo.com to the node hosting Router A and *.example.com to the node hosting Router B:

  • *.foo.com A IN 192.168.0.5

  • *.example.com A IN 192.168.1.9

Router Sharding Examples

This section describes router sharding using namespace and route labels.

Router Sharding Based on Namespace Labels

Figure 1. Router Sharding Based on Namespace Labels

  1. Configure a router with a namespace label selector:

    1. $ oc set env dc/router NAMESPACE_LABELS="router=r1"
  2. Because the router has a selector on the namespace, the router will handle routes only for matching namespaces. In order to make this selector match a namespace, label the namespace accordingly:

    1. $ oc label namespace default "router=r1"
  3. Now, if you create a route in the default namespace, the route is available in the default router:

    1. $ oc create -f route1.yaml
  4. Create a new project (namespace) and create a route, route2:

    1. $ oc new-project p1
    2. $ oc create -f route2.yaml

    Notice the route is not available in your router.

  5. Label namespace p1 with router=r1

    1. $ oc label namespace p1 "router=r1"

Adding this label makes the route available in the router.

Example

A router deployment finops-router is configured with the label selector NAMESPACE_LABELS="name in (finance, ops)", and a router deployment dev-router is configured with the label selector NAMESPACE_LABELS="name=dev".

If all routes are in namespaces labeled name=finance, name=ops, and name=dev, then this configuration effectively distributes your routes between the two router deployments.

In the above scenario, sharding becomes a special case of partitioning, with no overlapping subsets. Routes are divided between router shards.

The criteria for route selection govern how the routes are distributed. It is possible to have overlapping subsets of routes across router deployments.

Example

In addition to finops-router and dev-router in the example above, you also have devops-router, which is configured with a label selector NAMESPACE_LABELS="name in (dev, ops)".

The routes in namespaces labeled name=dev or name=ops now are serviced by two different router deployments. This becomes a case in which you have defined overlapping subsets of routes, as illustrated in the procedure in Router Sharding Based on Namespace Labels.

In addition, this enables you to create more complex routing rules, allowing the diversion of higher priority traffic to the dedicated finops-router while sending lower priority traffic to devops-router.

Router Sharding Based on Route Labels

NAMESPACE_LABELS allows filtering of the projects to service and selecting all the routes from those projects, but you may want to partition routes based on other criteria associated with the routes themselves. The ROUTE_LABELS selector allows you to slice-and-dice the routes themselves.

Example

A router deployment prod-router is configured with the label selector ROUTE_LABELS="mydeployment=prod", and a router deployment devtest-router is configured with the label selector ROUTE_LABELS="mydeployment in (dev, test)".

This configuration partitions routes between the two router deployments according to the routes’ labels, irrespective of their namespaces.

The example assumes you have all the routes you want to be serviced tagged with a label "mydeployment=<tag>".

Creating Router Shards

This section describes an advanced example of router sharding. Suppose there are 26 routes, named a — z, with various labels:

Possible labels on routes

  1. sla=high geo=east hw=modest dept=finance
  2. sla=medium geo=west hw=strong dept=dev
  3. sla=low dept=ops

These labels express the concepts including service level agreement, geographical location, hardware requirements, and department. The routes can have at most one label from each column. Some routes may have other labels or no labels at all.

Name(s)SLAGeoHWDeptOther Labels

a

high

east

modest

finance

type=static

b

west

strong

type=dynamic

c, d, e

low

modest

type=static

g — k

medium

strong

dev

l — s

high

modest

ops

t — z

west

type=dynamic

Here is a convenience script mkshard that illustrates how oc adm router, oc set env, and oc scale can be used together to make a router shard.

  1. #!/bin/bash
  2. # Usage: mkshard ID SELECTION-EXPRESSION
  3. id=$1
  4. sel="$2"
  5. router=router-shard-$id (1)
  6. oc adm router $router --replicas=0 (2)
  7. dc=dc/router-shard-$id (3)
  8. oc set env $dc ROUTE_LABELS="$sel" (4)
  9. oc scale $dc --replicas=3 (5)
1The created router has name router-shard-<id>.
2Specify no scaling for now.
3The deployment configuration for the router.
4Set the selection expression using oc set env. The selection expression is the value of the ROUTE_LABELS environment variable.
5Scale it up.

Running mkshard several times creates several routers:

RouterSelection ExpressionRoutes

router-shard-1

sla=high

a, l — s

router-shard-2

geo=west

b, t — z

router-shard-3

dept=dev

g — k

Modifying Router Shards

Because a router shard is a construct based on labels, you can modify either the labels (via oc label) or the selection expression (via oc set env).

This section extends the example started in the Creating Router Shards section, demonstrating how to change the selection expression.

Here is a convenience script modshard that modifies an existing router to use a new selection expression:

  1. #!/bin/bash
  2. # Usage: modshard ID SELECTION-EXPRESSION...
  3. id=$1
  4. shift
  5. router=router-shard-$id (1)
  6. dc=dc/$router (2)
  7. oc scale $dc --replicas=0 (3)
  8. oc set env $dc "$@" (4)
  9. oc scale $dc --replicas=3 (5)
1The modified router has name router-shard-<id>.
2The deployment configuration where the modifications occur.
3Scale it down.
4Set the new selection expression using oc set env. Unlike mkshard from the Creating Router Shards section, the selection expression specified as the non-ID arguments to modshard must include the environment variable name as well as its value.
5Scale it back up.

In modshard, the oc scale commands are not necessary if the deployment strategy for router-shard-<id> is Rolling.

For example, to expand the department for router-shard-3 to include ops as well as dev:

  1. $ modshard 3 ROUTE_LABELS='dept in (dev, ops)'

The result is that router-shard-3 now selects routes g — s (the combined sets of g — k and l — s).

This example takes into account that there are only three departments in this example scenario, and specifies a department to leave out of the shard, thus achieving the same result as the preceding example:

  1. $ modshard 3 ROUTE_LABELS='dept != finance'

This example specifies three comma-separated qualities, and results in only route b being selected:

  1. $ modshard 3 ROUTE_LABELS='hw=strong,type=dynamic,geo=west'

Similarly to ROUTE_LABELS, which involves a route’s labels, you can select routes based on the labels of the route’s namespace using the NAMESPACE_LABELS environment variable. This example modifies router-shard-3 to serve routes whose namespace has the label frequency=weekly:

  1. $ modshard 3 NAMESPACE_LABELS='frequency=weekly'

The last example combines ROUTE_LABELS and NAMESPACE_LABELS to select routes with label sla=low and whose namespace has the label frequency=weekly:

  1. $ modshard 3 \
  2. NAMESPACE_LABELS='frequency=weekly' \
  3. ROUTE_LABELS='sla=low'

Finding the Host Name of the Router

When exposing a service, a user can use the same route from the DNS name that external users use to access the application. The network administrator of the external network must make sure the host name resolves to the name of a router that has admitted the route. The user can set up their DNS with a CNAME that points to this host name. However, the user may not know the host name of the router. When it is not known, the cluster administrator can provide it.

The cluster administrator can use the --router-canonical-hostname option with the router’s canonical host name when creating the router. For example:

  1. # oc adm router myrouter --router-canonical-hostname="rtr.example.com"

This creates the ROUTER_CANONICAL_HOSTNAME environment variable in the router’s deployment configuration containing the host name of the router.

For routers that already exist, the cluster administrator can edit the router’s deployment configuration and add the ROUTER_CANONICAL_HOSTNAME environment variable:

  1. spec:
  2. template:
  3. spec:
  4. containers:
  5. - env:
  6. - name: ROUTER_CANONICAL_HOSTNAME
  7. value: rtr.example.com

The ROUTER_CANONICAL_HOSTNAME value is displayed in the route status for all routers that have admitted the route. The route status is refreshed every time the router is reloaded.

When a user creates a route, all of the active routers evaluate the route and, if conditions are met, admit it. When a router that defines the ROUTER_CANONICAL_HOSTNAME environment variable admits the route, the router places the value in the routerCanonicalHostname field in the route status. The user can examine the route status to determine which, if any, routers have admitted the route, select a router from the list, and find the host name of the router to pass along to the network administrator.

  1. status:
  2. ingress:
  3. conditions:
  4. lastTransitionTime: 2016-12-07T15:20:57Z
  5. status: "True"
  6. type: Admitted
  7. host: hello.in.mycloud.com
  8. routerCanonicalHostname: rtr.example.com
  9. routerName: myrouter
  10. wildcardPolicy: None

oc describe inclues the host name when available:

  1. $ oc describe route/hello-route3
  2. ...
  3. Requested Host: hello.in.mycloud.com exposed on router myroute (host rtr.example.com) 12 minutes ago

Using the above information, the user can ask the DNS administrator to set up a CNAME from the route’s host, hello.in.mycloud.com, to the router’s canonical hostname, rtr.example.com. This results in any traffic to hello.in.mycloud.com reaching the user’s application.

Customizing the Default Routing Subdomain

You can customize the suffix used as the default routing subdomain for your environment by modifying the master configuration file (the /etc/origin/master/master-config.yaml file by default). Routes that do not specify a host name would have one generated using this default routing subdomain.

The following example shows how you can set the configured suffix to v3.openshift.test:

  1. routingConfig:
  2. subdomain: v3.openshift.test

This change requires a restart of the master if it is running.

With the OKD master(s) running the above configuration, the generated host name for the example of a route named no-route-hostname without a host name added to a namespace mynamespace would be:

  1. no-route-hostname-mynamespace.v3.openshift.test

Forcing Route Host Names to a Custom Routing Subdomain

If an administrator wants to restrict all routes to a specific routing subdomain, they can pass the --force-subdomain option to the oc adm router command. This forces the router to override any host names specified in a route and generate one based on the template provided to the --force-subdomain option.

The following example runs a router, which overrides the route host names using a custom subdomain template ${name}-${namespace}.apps.example.com.

  1. $ oc adm router --force-subdomain='${name}-${namespace}.apps.example.com'

Using Wildcard Certificates

A TLS-enabled route that does not include a certificate uses the router’s default certificate instead. In most cases, this certificate should be provided by a trusted certificate authority, but for convenience you can use the OKD CA to create the certificate. For example:

  1. $ CA=/etc/origin/master
  2. $ oc adm ca create-server-cert --signer-cert=$CA/ca.crt \
  3. --signer-key=$CA/ca.key --signer-serial=$CA/ca.serial.txt \
  4. --hostnames='*.cloudapps.example.com' \
  5. --cert=cloudapps.crt --key=cloudapps.key

The oc adm ca create-server-cert command generates a certificate that is valid for two years. This can be altered with the —expire-days option, but for security reasons, it is recommended to not make it greater than this value.

Run oc adm commands only from the first master listed in the Ansible host inventory file, by default /etc/ansible/hosts.

The router expects the certificate and key to be in PEM format in a single file:

  1. $ cat cloudapps.crt cloudapps.key $CA/ca.crt > cloudapps.router.pem

From there you can use the --default-cert flag:

  1. $ oc adm router --default-cert=cloudapps.router.pem --service-account=router

Browsers only consider wildcards valid for subdomains one level deep. So in this example, the certificate would be valid for a.cloudapps.example.com but not for a.b.cloudapps.example.com.

Manually Redeploy Certificates

To manually redeploy the router certificates:

  1. Check to see if a secret containing the default router certificate was added to the router:

    1. $ oc set volume dc/router
    2. deploymentconfigs/router
    3. secret/router-certs as server-certificate
    4. mounted at /etc/pki/tls/private

    If the certificate is added, skip the following step and overwrite the secret.

  2. Make sure that you have a default certificate directory set for the following variable DEFAULT_CERTIFICATE_DIR:

    1. $ oc set env dc/router --list
    2. DEFAULT_CERTIFICATE_DIR=/etc/pki/tls/private

    If not, create the directory using the following command:

    1. $ oc set env dc/router DEFAULT_CERTIFICATE_DIR=/etc/pki/tls/private
  3. Export the certificate to PEM format:

    1. $ cat custom-router.key custom-router.crt custom-ca.crt > custom-router.crt
  4. Overwrite or create a router certificate secret:

    If the certificate secret was added to the router, overwrite the secret. If not, create a new secret.

    To overwrite the secret, run the following command:

    1. $ oc create secret generic router-certs --from-file=tls.crt=custom-router.crt --from-file=tls.key=custom-router.key --type=kubernetes.io/tls -o json --dry-run | oc replace -f -

    To create a new secret, run the following commands:

    1. $ oc create secret generic router-certs --from-file=tls.crt=custom-router.crt --from-file=tls.key=custom-router.key --type=kubernetes.io/tls
    2. $ oc set volume dc/router --add --mount-path=/etc/pki/tls/private --secret-name='router-certs' --name router-certs
  5. Deploy the router.

    1. $ oc rollout latest dc/router

Using Secured Routes

Currently, password protected key files are not supported. HAProxy prompts for a password upon starting and does not have a way to automate this process. To remove a passphrase from a keyfile, you can run:

  1. # openssl rsa -in <passwordProtectedKey.key> -out <new.key>

Here is an example of how to use a secure edge terminated route with TLS termination occurring on the router before traffic is proxied to the destination. The secure edge terminated route specifies the TLS certificate and key information. The TLS certificate is served by the router front end.

First, start up a router instance:

  1. # oc adm router --replicas=1 --service-account=router

Next, create a private key, csr and certificate for our edge secured route. The instructions on how to do that would be specific to your certificate authority and provider. For a simple self-signed certificate for a domain named www.example.test, see the example shown below:

  1. # sudo openssl genrsa -out example-test.key 2048
  2. #
  3. # sudo openssl req -new -key example-test.key -out example-test.csr \
  4. -subj "/C=US/ST=CA/L=Mountain View/O=OS3/OU=Eng/CN=www.example.test"
  5. #
  6. # sudo openssl x509 -req -days 366 -in example-test.csr \
  7. -signkey example-test.key -out example-test.crt

Generate a route using the above certificate and key.

  1. $ oc create route edge --service=my-service \
  2. --hostname=www.example.test \
  3. --key=example-test.key --cert=example-test.crt
  4. route "my-service" created

Look at its definition.

  1. $ oc get route/my-service -o yaml
  2. apiVersion: v1
  3. kind: Route
  4. metadata:
  5. name: my-service
  6. spec:
  7. host: www.example.test
  8. to:
  9. kind: Service
  10. name: my-service
  11. tls:
  12. termination: edge
  13. key: |
  14. -----BEGIN PRIVATE KEY-----
  15. [...]
  16. -----END PRIVATE KEY-----
  17. certificate: |
  18. -----BEGIN CERTIFICATE-----
  19. [...]
  20. -----END CERTIFICATE-----

Make sure your DNS entry for www.example.test points to your router instance(s) and the route to your domain should be available. The example below uses curl along with a local resolver to simulate the DNS lookup:

  1. # routerip="4.1.1.1" # replace with IP address of one of your router instances.
  2. # curl -k --resolve www.example.test:443:$routerip https://www.example.test/

Using Wildcard Routes (for a Subdomain)

The HAProxy router has support for wildcard routes, which are enabled by setting the ROUTER_ALLOW_WILDCARD_ROUTES environment variable to true. Any routes with a wildcard policy of Subdomain that pass the router admission checks will be serviced by the HAProxy router. Then, the HAProxy router exposes the associated service (for the route) per the route’s wildcard policy.

To change a route’s wildcard policy, you must remove the route and recreate it with the updated wildcard policy. Editing only the route’s wildcard policy in a route’s .yaml file does not work.

  1. $ oc adm router --replicas=0 ...
  2. $ oc set env dc/router ROUTER_ALLOW_WILDCARD_ROUTES=true
  3. $ oc scale dc/router --replicas=1

Learn how to configure the web console for wildcard routes.

Using a Secure Wildcard Edge Terminated Route

This example reflects TLS termination occurring on the router before traffic is proxied to the destination. Traffic sent to any hosts in the subdomain example.org (*.example.org) is proxied to the exposed service.

The secure edge terminated route specifies the TLS certificate and key information. The TLS certificate is served by the router front end for all hosts that match the subdomain (*.example.org).

  1. Start up a router instance:

    1. $ oc adm router --replicas=0 --service-account=router
    2. $ oc set env dc/router ROUTER_ALLOW_WILDCARD_ROUTES=true
    3. $ oc scale dc/router --replicas=1
  2. Create a private key, certificate signing request (CSR), and certificate for the edge secured route.

    The instructions on how to do this are specific to your certificate authority and provider. For a simple self-signed certificate for a domain named *.example.test, see this example:

    1. # sudo openssl genrsa -out example-test.key 2048
    2. #
    3. # sudo openssl req -new -key example-test.key -out example-test.csr \
    4. -subj "/C=US/ST=CA/L=Mountain View/O=OS3/OU=Eng/CN=*.example.test"
    5. #
    6. # sudo openssl x509 -req -days 366 -in example-test.csr \
    7. -signkey example-test.key -out example-test.crt
  3. Generate a wildcard route using the above certificate and key:

    1. $ cat > route.yaml <<REOF
    2. apiVersion: v1
    3. kind: Route
    4. metadata:
    5. name: my-service
    6. spec:
    7. host: www.example.test
    8. wildcardPolicy: Subdomain
    9. to:
    10. kind: Service
    11. name: my-service
    12. tls:
    13. termination: edge
    14. key: "$(perl -pe 's/\n/\\n/' example-test.key)"
    15. certificate: "$(perl -pe 's/\n/\\n/' example-test.cert)"
    16. REOF
    17. $ oc create -f route.yaml

    Ensure your DNS entry for *.example.test points to your router instance(s) and the route to your domain is available.

    This example uses curl with a local resolver to simulate the DNS lookup:

    1. # routerip="4.1.1.1" # replace with IP address of one of your router instances.
    2. # curl -k --resolve www.example.test:443:$routerip https://www.example.test/
    3. # curl -k --resolve abc.example.test:443:$routerip https://abc.example.test/
    4. # curl -k --resolve anyname.example.test:443:$routerip https://anyname.example.test/

For routers that allow wildcard routes (ROUTER_ALLOW_WILDCARD_ROUTES set to true), there are some caveats to the ownership of a subdomain associated with a wildcard route.

Prior to wildcard routes, ownership was based on the claims made for a host name with the namespace with the oldest route winning against any other claimants. For example, route r1 in namespace ns1 with a claim for one.example.test would win over another route r2 in namespace ns2 for the same host name one.example.test if route r1 was older than route r2.

In addition, routes in other namespaces were allowed to claim non-overlapping hostnames. For example, route rone in namespace ns1 could claim www.example.test and another route rtwo in namespace d2 could claim c3po.example.test.

This is still the case if there are no wildcard routes claiming that same subdomain (example.test in the above example).

However, a wildcard route needs to claim all of the host names within a subdomain (host names of the form \*.example.test). A wildcard route’s claim is allowed or denied based on whether or not the oldest route for that subdomain (example.test) is in the same namespace as the wildcard route. The oldest route can be either a regular route or a wildcard route.

For example, if there is already a route eldest that exists in the ns1 namespace that claimed a host named owner.example.test and, if at a later point in time, a new wildcard route wildthing requesting for routes in that subdomain (example.test) is added, the claim by the wildcard route will only be allowed if it is the same namespace (ns1) as the owning route.

The following examples illustrate various scenarios in which claims for wildcard routes will succeed or fail.

In the example below, a router that allows wildcard routes will allow non-overlapping claims for hosts in the subdomain example.test as long as a wildcard route has not claimed a subdomain.

  1. $ oc adm router ...
  2. $ oc set env dc/router ROUTER_ALLOW_WILDCARD_ROUTES=true
  3. $ oc project ns1
  4. $ oc expose service myservice --hostname=owner.example.test
  5. $ oc expose service myservice --hostname=aname.example.test
  6. $ oc expose service myservice --hostname=bname.example.test
  7. $ oc project ns2
  8. $ oc expose service anotherservice --hostname=second.example.test
  9. $ oc expose service anotherservice --hostname=cname.example.test
  10. $ oc project otherns
  11. $ oc expose service thirdservice --hostname=emmy.example.test
  12. $ oc expose service thirdservice --hostname=webby.example.test

In the example below, a router that allows wildcard routes will not allow the claim for owner.example.test or aname.example.test to succeed since the owning namespace is ns1.

  1. $ oc adm router ...
  2. $ oc set env dc/router ROUTER_ALLOW_WILDCARD_ROUTES=true
  3. $ oc project ns1
  4. $ oc expose service myservice --hostname=owner.example.test
  5. $ oc expose service myservice --hostname=aname.example.test
  6. $ oc project ns2
  7. $ oc expose service secondservice --hostname=bname.example.test
  8. $ oc expose service secondservice --hostname=cname.example.test
  9. $ # Router will not allow this claim with a different path name `/p1` as
  10. $ # namespace `ns1` has an older route claiming host `aname.example.test`.
  11. $ oc expose service secondservice --hostname=aname.example.test --path="/p1"
  12. $ # Router will not allow this claim as namespace `ns1` has an older route
  13. $ # claiming host name `owner.example.test`.
  14. $ oc expose service secondservice --hostname=owner.example.test
  15. $ oc project otherns
  16. $ # Router will not allow this claim as namespace `ns1` has an older route
  17. $ # claiming host name `aname.example.test`.
  18. $ oc expose service thirdservice --hostname=aname.example.test

In the example below, a router that allows wildcard routes will allow the claim for `\*.example.test to succeed since the owning namespace is ns1 and the wildcard route belongs to that same namespace.

  1. $ oc adm router ...
  2. $ oc set env dc/router ROUTER_ALLOW_WILDCARD_ROUTES=true
  3. $ oc project ns1
  4. $ oc expose service myservice --hostname=owner.example.test
  5. $ # Reusing the route.yaml from the previous example.
  6. $ # spec:
  7. $ # host: www.example.test
  8. $ # wildcardPolicy: Subdomain
  9. $ oc create -f route.yaml # router will allow this claim.

In the example below, a router that allows wildcard routes will not allow the claim for `\*.example.test to succeed since the owning namespace is ns1 and the wildcard route belongs to another namespace cyclone.

  1. $ oc adm router ...
  2. $ oc set env dc/router ROUTER_ALLOW_WILDCARD_ROUTES=true
  3. $ oc project ns1
  4. $ oc expose service myservice --hostname=owner.example.test
  5. $ # Switch to a different namespace/project.
  6. $ oc project cyclone
  7. $ # Reusing the route.yaml from a prior example.
  8. $ # spec:
  9. $ # host: www.example.test
  10. $ # wildcardPolicy: Subdomain
  11. $ oc create -f route.yaml # router will deny (_NOT_ allow) this claim.

Similarly, once a namespace with a wildcard route claims a subdomain, only routes within that namespace can claim any hosts in that same subdomain.

In the example below, once a route in namespace ns1 with a wildcard route claims subdomain example.test, only routes in the namespace ns1 are allowed to claim any hosts in that same subdomain.

  1. $ oc adm router ...
  2. $ oc set env dc/router ROUTER_ALLOW_WILDCARD_ROUTES=true
  3. $ oc project ns1
  4. $ oc expose service myservice --hostname=owner.example.test
  5. $ oc project otherns
  6. $ # namespace `otherns` is allowed to claim for other.example.test
  7. $ oc expose service otherservice --hostname=other.example.test
  8. $ oc project ns1
  9. $ # Reusing the route.yaml from the previous example.
  10. $ # spec:
  11. $ # host: www.example.test
  12. $ # wildcardPolicy: Subdomain
  13. $ oc create -f route.yaml # Router will allow this claim.
  14. $ # In addition, route in namespace otherns will lose its claim to host
  15. $ # `other.example.test` due to the wildcard route claiming the subdomain.
  16. $ # namespace `ns1` is allowed to claim for deux.example.test
  17. $ oc expose service mysecondservice --hostname=deux.example.test
  18. $ # namespace `ns1` is allowed to claim for deux.example.test with path /p1
  19. $ oc expose service mythirdservice --hostname=deux.example.test --path="/p1"
  20. $ oc project otherns
  21. $ # namespace `otherns` is not allowed to claim for deux.example.test
  22. $ # with a different path '/otherpath'
  23. $ oc expose service otherservice --hostname=deux.example.test --path="/otherpath"
  24. $ # namespace `otherns` is not allowed to claim for owner.example.test
  25. $ oc expose service yetanotherservice --hostname=owner.example.test
  26. $ # namespace `otherns` is not allowed to claim for unclaimed.example.test
  27. $ oc expose service yetanotherservice --hostname=unclaimed.example.test

In the example below, different scenarios are shown, in which the owner routes are deleted and ownership is passed within and across namespaces. While a route claiming host eldest.example.test in the namespace ns1 exists, wildcard routes in that namespace can claim subdomain example.test. When the route for host eldest.example.test is deleted, the next oldest route senior.example.test would become the oldest route and would not affect any other routes. Once the route for host senior.example.test is deleted, the next oldest route junior.example.test becomes the oldest route and block the wildcard route claimant.

  1. $ oc adm router ...
  2. $ oc set env dc/router ROUTER_ALLOW_WILDCARD_ROUTES=true
  3. $ oc project ns1
  4. $ oc expose service myservice --hostname=eldest.example.test
  5. $ oc expose service seniorservice --hostname=senior.example.test
  6. $ oc project otherns
  7. $ # namespace `otherns` is allowed to claim for other.example.test
  8. $ oc expose service juniorservice --hostname=junior.example.test
  9. $ oc project ns1
  10. $ # Reusing the route.yaml from the previous example.
  11. $ # spec:
  12. $ # host: www.example.test
  13. $ # wildcardPolicy: Subdomain
  14. $ oc create -f route.yaml # Router will allow this claim.
  15. $ # In addition, route in namespace otherns will lose its claim to host
  16. $ # `junior.example.test` due to the wildcard route claiming the subdomain.
  17. $ # namespace `ns1` is allowed to claim for dos.example.test
  18. $ oc expose service mysecondservice --hostname=dos.example.test
  19. $ # Delete route for host `eldest.example.test`, the next oldest route is
  20. $ # the one claiming `senior.example.test`, so route claims are unaffacted.
  21. $ oc delete route myservice
  22. $ # Delete route for host `senior.example.test`, the next oldest route is
  23. $ # the one claiming `junior.example.test` in another namespace, so claims
  24. $ # for a wildcard route would be affected. The route for the host
  25. $ # `dos.example.test` would be unaffected as there are no other wildcard
  26. $ # claimants blocking it.
  27. $ oc delete route seniorservice

Using the Container Network Stack

The OKD router runs inside a container and the default behavior is to use the network stack of the host (i.e., the node where the router container runs). This default behavior benefits performance because network traffic from remote clients does not need to take multiple hops through user space to reach the target service and container.

Additionally, this default behavior enables the router to get the actual source IP address of the remote connection rather than getting the node’s IP address. This is useful for defining ingress rules based on the originating IP, supporting sticky sessions, and monitoring traffic, among other uses.

This host network behavior is controlled by the --host-network router command line option, and the default behaviour is the equivalent of using --host-network=true. If you wish to run the router with the container network stack, use the --host-network=false option when creating the router. For example:

  1. $ oc adm router --service-account=router --host-network=false

Internally, this means the router container must publish the 80 and 443 ports in order for the external network to communicate with the router.

Running with the container network stack means that the router sees the source IP address of a connection to be the NATed IP address of the node, rather than the actual remote IP address.

On OKD clusters using multi-tenant network isolation, routers on a non-default namespace with the —host-network=false option will load all routes in the cluster, but routes across the namespaces will not be reachable due to network isolation. With the —host-network=true option, routes bypass the container network and it can access any pod in the cluster. If isolation is needed in this case, then do not add routes across the namespaces.

Using the Dynamic Configuration Manager

You can configure the HAProxy router to support the dynamic configuration manager.

The dynamic configuration manager brings certain types of routes online without requiring HAProxy reload downtime. It handles any route and endpoint life-cycle events such as route and endpoint addition|deletion|update.

Enable the dynamic configuration manager by setting the ROUTER_HAPROXY_CONFIG_MANAGER environment variable to true:

  1. $ oc set env dc/<router_name> ROUTER_HAPROXY_CONFIG_MANAGER='true'

If the dynamic configuration manager cannot dynamically configure HAProxy, it rewrites the configuration and reloads the HAProxy process. For example, if a new route contains custom annotations, such as custom timeouts, or if the route requires custom TLS configuration.

The dynamic configuration internally uses the HAProxy socket and configuration API with a pool of pre-allocated routes and back end servers. The pre-allocated pool of routes is created using route blueprints. The default set of blueprints supports unsecured routes, edge secured routes without any custom TLS configuration, and passthrough routes.

re-encrypt routes require custom TLS configuration information, so extra configuration is needed in order to use them with the dynamic configuration manager.

Extend the blueprints that the dynamic configuration manager can use by setting the ROUTER_BLUEPRINT_ROUTE_NAMESPACE and optionally the ROUTER_BLUEPRINT_ROUTE_LABELS environment variables.

All routes, or the routes that match the route labels, in the blueprint route namespace are processed as custom blueprints similar to the default set of blueprints. This includes re-encrypt routes or routes that use custom annotations or routes with custom TLS configuration.

The following procedure assumes you have created three route objects: reencrypt-blueprint, annotated-edge-blueprint, and annotated-unsecured-blueprint. See Route Types for an example of the different route type objects.

Procedure

  1. Create a new project:

    1. $ oc new-project namespace_name
  2. Create a new route. This method exposes an existing service:

    1. $ oc create route edge edge_route_name --key=/path/to/key.pem \
    2. --cert=/path/to/cert.pem --service=<service> --port=8443
  3. Label the route:

    1. $ oc label route edge_route_name type=route_label_1
  4. Create three different routes from route object definitions. All have the label type=route_label_1:

    1. $ oc create -f reencrypt-blueprint.yaml
    2. $ oc create -f annotated-edge-blueprint.yaml
    3. $ oc create -f annotated-unsecured-blueprint.json

    You can also remove a label from a route, which prevents it from being used as a blueprint route. For example, to prevent the annotated-unsecured-blueprint from being used as a blueprint route:

    1. $ oc label route annotated-unsecured-blueprint type-
  5. Create a new router to be used for the blueprint pool:

    1. $ oc adm router
  6. Set the environment variables for the new router:

    1. $ oc set env dc/router ROUTER_HAPROXY_CONFIG_MANAGER=true \
    2. ROUTER_BLUEPRINT_ROUTE_NAMESPACE=namespace_name \
    3. ROUTER_BLUEPRINT_ROUTE_LABELS="type=route_label_1"

    All routes in the namespace or project namespace_name with label type=route_label_1 can be processed and used as custom blueprints.

    Note that you can also add, update, or remove blueprints by managing the routes as you would normally in that namespace namespace_name. The dynamic configuration manager watches for changes to routes in the namespace namespace_name similar to how the router watches for routes and services.

  7. The pool sizes of the pre-allocated routes and back end servers can be controlled with the ROUTER_BLUEPRINT_ROUTE_POOL_SIZE, which defaults to 10, and ROUTER_MAX_DYNAMIC_SERVERS, which defaults to 5, environment variables. You can also control how often changes made by the dynamic configuration manager are committed to disk, which is when the HAProxy configuration is re-written and the HAProxy process is reloaded. The default is one hour, or 3600 seconds, or when the dynamic configuration manager runs out of pool space. The COMMIT_INTERVAL environment variable controls this setting:

    1. $ oc set env dc/router -c router ROUTER_BLUEPRINT_ROUTE_POOL_SIZE=20 \
    2. ROUTER_MAX_DYNAMIC_SERVERS=3 COMMIT_INTERVAL=6h

    The example increases the pool size for each blueprint route to 20, reduces the number of dynamic servers to 3, and increases the commit interval to 6 hours.

Exposing Router Metrics

The HAProxy router metrics are, by default, exposed or published in Prometheus format for consumption by external metrics collection and aggregation systems (e.g. Prometheus, statsd). Metrics are also available directly from the HAProxy router in its own HTML format for viewing in a browser or CSV download. These metrics include the HAProxy native metrics and some controller metrics.

When you create a router using the following command, OKD makes metrics available in Prometheus format on the stats port, by default 1936.

  1. $ oc adm router --service-account=router
  • To extract the raw statistics in Prometheus format run the following command:

    1. curl <user>:<password>@<router_IP>:<STATS_PORT>

    For example:

    1. $ curl admin:sLzdR6SgDJ@10.254.254.35:1936/metrics

    You can get the information you need to access the metrics from the router service annotations:

    1. $ oc edit service <router-name>
    2. apiVersion: v1
    3. kind: Service
    4. metadata:
    5. annotations:
    6. prometheus.io/port: "1936"
    7. prometheus.io/scrape: "true"
    8. prometheus.openshift.io/password: IImoDqON02
    9. prometheus.openshift.io/username: admin

    The prometheus.io/port is the stats port, by default 1936. You might need to configure your firewall to permit access. Use the previous user name and password to access the metrics. The path is /metrics.

    1. $ curl <user>:<password>@<router_IP>:<STATS_PORT>
    2. for example:
    3. $ curl admin:sLzdR6SgDJ@10.254.254.35:1936/metrics
    4. ...
    5. # HELP haproxy_backend_connections_total Total number of connections.
    6. # TYPE haproxy_backend_connections_total gauge
    7. haproxy_backend_connections_total{backend="http",namespace="default",route="hello-route"} 0
    8. haproxy_backend_connections_total{backend="http",namespace="default",route="hello-route-alt"} 0
    9. haproxy_backend_connections_total{backend="http",namespace="default",route="hello-route01"} 0
    10. ...
    11. # HELP haproxy_exporter_server_threshold Number of servers tracked and the current threshold value.
    12. # TYPE haproxy_exporter_server_threshold gauge
    13. haproxy_exporter_server_threshold{type="current"} 11
    14. haproxy_exporter_server_threshold{type="limit"} 500
    15. ...
    16. # HELP haproxy_frontend_bytes_in_total Current total of incoming bytes.
    17. # TYPE haproxy_frontend_bytes_in_total gauge
    18. haproxy_frontend_bytes_in_total{frontend="fe_no_sni"} 0
    19. haproxy_frontend_bytes_in_total{frontend="fe_sni"} 0
    20. haproxy_frontend_bytes_in_total{frontend="public"} 119070
    21. ...
    22. # HELP haproxy_server_bytes_in_total Current total of incoming bytes.
    23. # TYPE haproxy_server_bytes_in_total gauge
    24. haproxy_server_bytes_in_total{namespace="",pod="",route="",server="fe_no_sni",service=""} 0
    25. haproxy_server_bytes_in_total{namespace="",pod="",route="",server="fe_sni",service=""} 0
    26. haproxy_server_bytes_in_total{namespace="default",pod="docker-registry-5-nk5fz",route="docker-registry",server="10.130.0.89:5000",service="docker-registry"} 0
    27. haproxy_server_bytes_in_total{namespace="default",pod="hello-rc-vkjqx",route="hello-route",server="10.130.0.90:8080",service="hello-svc-1"} 0
    28. ...
  • To get metrics in a browser:

    1. Delete the following environment variables from the router deployment configuration file:

      1. $ oc edit dc router
      2. - name: ROUTER_LISTEN_ADDR
      3. value: 0.0.0.0:1936
      4. - name: ROUTER_METRICS_TYPE
      5. value: haproxy
    2. Patch the router readiness probe to use the same path as the liveness probe as it is now served by the haproxy router:

      1. $ oc patch dc router -p '"spec": {"template": {"spec": {"containers": [{"name": "router","readinessProbe": {"httpGet": {"path": "/healthz"}}}]}}}'
    3. Launch the stats window using the following URL in a browser, where the STATS_PORT value is 1936 by default:

      1. http://admin:<Password>@<router_IP>:<STATS_PORT>

      You can get the stats in CSV format by adding ;csv to the URL:

      For example:

      1. http://admin:<Password>@<router_IP>:1936;csv

      To get the router IP, admin name, and password:

      1. oc describe pod <router_pod>
  • To suppress metrics collection:

    1. $ oc adm router --service-account=router --stats-port=0

ARP Cache Tuning for Large-scale Clusters

In OKD clusters with large numbers of routes (greater than the value of net.ipv4.neigh.default.gc_thresh3, which is 65536 by default), you must increase the default values of sysctl variables on each node in the cluster running the router pod to allow more entries in the ARP cache.

When the problem is occuring, the kernel messages would be similar to the following:

  1. [ 1738.811139] net_ratelimit: 1045 callbacks suppressed
  2. [ 1743.823136] net_ratelimit: 293 callbacks suppressed

When this issue occurs, the oc commands might start to fail with the following error:

  1. Unable to connect to the server: dial tcp: lookup <hostname> on <ip>:<port>: write udp <ip>:<port>-><ip>:<port>: write: invalid argument

To verify the actual amount of ARP entries for IPv4, run the following:

  1. # ip -4 neigh show nud all | wc -l

If the number begins to approach the net.ipv4.neigh.default.gc_thresh3 threshold, increase the values. Get the current value by running:

  1. # sysctl net.ipv4.neigh.default.gc_thresh1
  2. net.ipv4.neigh.default.gc_thresh1 = 128
  3. # sysctl net.ipv4.neigh.default.gc_thresh2
  4. net.ipv4.neigh.default.gc_thresh2 = 512
  5. # sysctl net.ipv4.neigh.default.gc_thresh3
  6. net.ipv4.neigh.default.gc_thresh3 = 1024

The following sysctl sets the variables to the OKD current default values.

  1. # sysctl net.ipv4.neigh.default.gc_thresh1=8192
  2. # sysctl net.ipv4.neigh.default.gc_thresh2=32768
  3. # sysctl net.ipv4.neigh.default.gc_thresh3=65536

To make these settings permanent, see this document.

Protecting Against DDoS Attacks

Add timeout http-request to the default HAProxy router image to protect the deployment against distributed denial-of-service (DDoS) attacks (for example, slowloris):

  1. # and the haproxy stats socket is available at /var/run/haproxy.stats
  2. global
  3. stats socket ./haproxy.stats level admin
  4. defaults
  5. option http-server-close
  6. mode http
  7. timeout http-request 5s
  8. timeout connect 5s (1)
  9. timeout server 10s
  10. timeout client 30s
1timeout http-request is set up to 5 seconds. HAProxy gives a client 5 seconds to send its whole HTTP request. Otherwise, HAProxy shuts the connection with an error.

Also, when the environment variable **ROUTER_SLOWLORIS_TIMEOUT** is set, it limits the amount of time a client has to send the whole HTTP request. Otherwise, HAProxy will shut down the connection.

Setting the environment variable allows information to be captured as part of the router’s deployment configuration and does not require manual modification of the template, whereas manually adding the HAProxy setting requires you to rebuild the router pod and maintain your router template file.

Using annotations implements basic DDoS protections in the HAProxy template router, including the ability to limit the:

  • number of concurrent TCP connections

  • rate at which a client can request TCP connections

  • rate at which HTTP requests can be made

These are enabled on a per route basis because applications can have extremely different traffic patterns.

Table 1. HAProxy Template Router Settings
SettingDescription

haproxy.router.openshift.io/rate-limit-connections

Enables the settings be configured (when set to true, for example).

haproxy.router.openshift.io/rate-limit-connections.concurrent-tcp

The number of concurrent TCP connections that can be made by the same IP address on this route.

haproxy.router.openshift.io/rate-limit-connections.rate-tcp

The number of TCP connections that can be opened by a client IP.

haproxy.router.openshift.io/rate-limit-connections.rate-http

The number of HTTP requests that a client IP can make in a 3-second period.

Enable HAProxy Threading

Enabled threading with the --threads flag. This flag specifies the number of threads that the HAProxy router will use.