piątek, 17 grudnia 2021

3 ways to collect metrics from OpenShift built in Prometheus

In this blog post I'll describe 3 methods of collecting metrics data from OpenShift 4 built in Prometheus. This might be especially useful if you are running a central monitoring solution outside of OpenShift and you would like to integrate it with metrics collected by OpenShift built in Prometheus.

1. Thanos Querier

The Thanos Querier aggregates and optionally deduplicates core OpenShift Container Platform metrics and metrics for user-defined projects under a single, multi-tenant interface. Thanos Querier expose route which can be queried by authorized clients using promql semantics. In order to authorize requests you must provide a bearer token belonging to a user or service account which has at minimum cluster-monitoring-view OpenShift role granted. 

Here is an example how to create service account with cluster-monitoring-view role and query Thanos Querier endpoint:

oc project openshift-monitoring
SA=querier
oc create sa $SA
oc adm policy add-cluster-role-to-user cluster-monitoring-view -z $SA

TOKEN=$(oc sa get-token $SA -n openshift-monitoring)
URL=$(oc get route thanos-querier --template='{{.spec.host}}' -n openshift-monitoring)
QUERY=node_cpu_seconds_total

curl -k -H "Authorization: Bearer $TOKEN" https://$URL/api/v1/query?query=$QUERY

2. Prometheus Federation

Federation allows a Prometheus server to scrape selected time series from another Prometheus server. Each Prometheus instance exposes /federate endpoint which might be queried using the exposed OpenShift route. For request authorization the same rules apply as for Thanos Querier authorization:

URL=$(oc get route prometheus-k8s --template='{{.spec.host}}' -n openshift-monitoring)

QUERY='match[]={__name__=~"node_cpu_seconds_total|node_memory_MemAvailable_bytes"}'


curl -G --data-urlencode "$QUERY" -k -H "Authorization: Bearer $TOKEN" https://$URL/federate

3. Remote write

Both methods described above require external systems to periodically pull metrics from Prometheus. On the contrary, remote write allows you to push metrics from Prometheus to remote systems.

Remote write configuration must be done in the cluster-monitoring-config config map located in openshift-monitoring namespace:

apiVersion: v1

kind: ConfigMap

metadata:

  name: cluster-monitoring-config

  namespace: openshift-monitoring

data:

  config.yaml: |

    prometheusK8s:

      remoteWrite:

      - url: "https://remote-write.endpoint"

        writeRelabelConfigs:

        - sourceLabels: [__name__]

          regex: 'node_cpu_seconds_total|node_memory_MemAvailable_bytes'

          action: keep

Above you can see a fairly simple configuration which will push only 2 filtered metrics data to the remote endpoint in default intervals of 1 minute. There is much more configuration possible as per Prometheus documentation. All these configurations can be created in the cluster-monitoring-config config map, but please note you must follow naming conventions according to Prometheus Operator specification which is slightly different from Prometheus documentation.

środa, 17 listopada 2021

Secure your workloads in OpenShift Container Platform

In this blog post I will discuss the OpenShift security use cases that Red Hat Advanced Cluster Security for Kubernetes addresses and identify the benefits of taking a Kubernetes-native approach to securing your containerized applications in OpenShift.

Containers and microservices initiated a shift in application infrastructure, and Kubernetes has emerged as one of the most quickly adopted technologies ever, helping companies automate the management of these application building blocks. This massive change in infrastructure has driven a parallel change in security, as new tooling and processes are needed to apply controls to the cloud-native stack.

In today’s Kubernetes world, it is no longer adequate to secure just your images and containers. You need a security platform that protects your entire Kubernetes environment across the entire applications lifecycle covering Build, Deploy and Runtime applications lifecycle phases.


 

 

 


 

Source: https://www.tigera.io/lp/kubernetes-security-and-observability-ebook/

Benefits of Kubernetes-Native Security

Red Hat Advanced Cluster Security for Kubernetes was purpose-built for the modern cloud-native stack. We have built multiple deep integrations with Kubernetes into our platform, making your security as portable, scalable, and resilient as your hybrid cloud infrastructure. This Kubernetes-native approach also delivers the most comprehensive set of container and Kubernetes security capabilities across the full application life cycle.


These advantages, derived from our tight integrations with Kubernetes, enable a better security outcome. While many container security providers highlight a common set of use cases, how you deliver those use cases impacts the result. The following discussion of the top OpenShift security use cases illustrates the advantages of applying a Kubernetes-native architecture.

Visibility

You cannot secure what you cannot see. As a first step, you must gain visibility into your OpenShift environment. You should know what images you are using, understand their provenance, whether they contain any vulnerabilities and their severity level, and that is just the start. You must also know which pods, namespaces, and deployments are running vulnerable containers and their attack surface and potential blast radius in the event of a breach.


Red Hat Advanced Cluster Security for Kubernetes provides visibility into your entire OpenShift environment and accompanying security issues, including the images, containers, and vulnerabilities with CVE and severity scores
This visibility is enhanced with contextual data from Kubernetes, such as allowed network paths, runtime process execution, secrets exposure, and other attributes of the environment.

Vulnerability Management

One of the most critical steps in securing containers in OpenShift is to prevent images with known, fixable vulnerabilities from being used as well as to identify and stop running containers that have vulnerabilities. You must also run on-demand vulnerability searches across images, running deployments, and clusters to enforce policies at build, deploy, and runtime.

A vulnerability management solution must also integrate with your CI/CD pipeline to fail a build if it contains a vulnerability while providing the developer details on why the build failed and how to remediate it.

Red Hat Advanced Cluster Security for Kubernetes delivers full life cycle image and container scanning. We combine details about vulnerabilities with Kubernetes data and the life cycle stage that the vulnerability impacts in order to quantify the security risk that a given vulnerability poses to your environment. This also allows us to pinpoint which pods, namespaces, deployments, and clusters are impacted by a given vulnerability.

Risk Profiling

A common pain point is being overwhelmed with security alerts and incidents that need investigation without any guidance on prioritization. This approach inevitably leads to instances where high-risk security issues trail low/medium-risk issues in remediation simply because teams cannot identify which problems present the highest risk. Or in the worst case scenario, without prioritization, nothing is remediated.

Red Hat Advanced Cluster Security for Kubernetes provides a numerical risk-based ranking for each deployment based on information across the entire application life cycle. We correlate image vulnerabilities and their severity with rich contextual data that empowers users to understand which deployments are in need of immediate remediation so that the highest risk deployments are addressed first.

Network Segmentation

Containers pose a unique networking challenge because containers communicate with each other across nodes and clusters (east-west traffic) and outside endpoints (north-south traffic). As a result, a single container breach has the potential to impact every other container. Therefore, it is imperative to limit a container’s communication in adherence with least privilege principles without inhibiting your container’s functional goals.

Our approach to network segmentation leverages the built-in feature in Kubernetes known as Network Policies, which gives robust and portable enforcement that scales as Kubernetes scales. This also ensures that security, operations, and development teams use a single source of truth and consistent information to effectively restrict network access.  

Runtime Threat Detection and Response

Once container images are built and deployed into production, they are exposed to new security challenges and external adversaries. The primary goal of security in the runtime phase is to detect and respond to malicious activity in an automated and scalable way while minimizing false positives and alert fatigue.

Red Hat Advanced Cluster Security for Kubernetes combines automated process discovery and behavioral baselining with automatically creating an allowed list of processes to determine actual threats from benign anomalies.

It also provides pre-configured threat profiles that detect common threats including cryptocurrency mining, privilege escalation, and various other exploits. Because it uses Kubernetes-native controls to mitigate threats with actions such as killing pods and restarting them fresh or scaling deployments to zero, it ensures incident response does not result in application downtime or pose other operational risk.

Configuration Management

The configuration options for container and Kubernetes environments run deep and can be challenging for security teams to get right. In sprawling container and Kubernetes environments, it is not advisable to manually check each security configuration for each asset to assess its risk.

While the CIS Benchmarks for Docker and Kubernetes provide helpful guidance and a useful framework for hardening your environment, they contain hundreds of checks for different configuration settings. Ensuring continuous adherence to the CIS benchmarks and other configuration best practices can be challenging without automation.

Red Hat Advanced Cluster Security for Kubernetes gives a deployment-centric view of how their images, containers, and deployments are configured prior to running to identify missed best practices and recommendations. It evaluates how you are using role-based access control (RBAC) to understand user and service account privileges to identify risky configurations. It also tracks the use of secrets to identify unnecessary exposure so you can proactively limit its access.

When it detects misconfigurations, it allows you to build custom policies or use one of its out-of-the-box rules to enforce better configuration – at build time with CI/CD pipeline integration or at deploy time using dynamic admission control.

Compliance

DevOps moves fast and relies on automation for continuous improvement; therefore, organizations need a compliance solution built to complement, not inhibit, DevOps activities. You not only need to adhere to industry compliance requirements but also show proof of continuous adherence.

Lastly, you also need to adhere to internal policies for security configurations and other best practices to prevent non-compliant builds or deployments from being pushed to production.

Our Kubernetes-native security solution comes pre-built with compliance checks for CIS benchmarks for Docker, Kubernetes and OpenShift as well as other industry standards such as PCI, HIPAA, and NIST SP 800-90 and SP 800-53. Compliance reports can be generated with a single click and handed to auditors as evidence.

Red Hat Advanced Cluster Security for Kubernetes delivers the next generation in container security, with a Kubernetes-native architecture that is both container-native and Kubernetes-native. Leveraging the declarative data and built-in controls of Kubernetes minimizes operational risk, accelerates developer productivity, and reduces operational cost while immediately improving your security posture across applications lifecycle Build, Deploy and Runtime phases.

wtorek, 17 sierpnia 2021

Expose MongoDB as REST service using Camel Quarkus

In this blog post I'll show you how to create a REST service which exposes CRUD functionality from a MongoDB database using Camel Quarkus framework. 

Why Camel Quarkus?

As a Java developer I prefer to use Java frameworks to develop my code. With Apache Camel I can develop faster integration logic without need to take care of low level boilerplate coding and I can focus mostly on business logic. With Camel XML dsl I can follow low code approach using XML instead of Java, which is even faster and less error prone!

With Quarkus I can create super thin application binaries and container images which allows me to run applications and containers with minimal resources footprint compared to regular JVM and start them super fast in milliseconds.

Camel Quarkus combines the best of both worlds: Java integration developer experience of Camel with resource consumption optimization of Quarkus.

Let's get started!

The first step is to create Camel Quarkus maven project on your local machine

mvn \
io.quarkus:quarkus-maven-plugin:1.13.7.Final:create \
-DprojectGroupId=org.redhat \
-DprojectArtifactId=camel-quarkus-mongodb-client \
-DplatformGroupId=io.quarkus \
-DplatformVersion=1.13.7.Final \
-Dextensions=camel-quarkus-xml-io-dsl,camel-quarkus-direct,camel-quarkus-mongodb,camel-quarkus-log,camel-quarkus-jackson,camel-quarkus-http,camel-quarkus-rest,camel-quarkus-bean

Next we must edit our application configuration and add required parameters. Please check Quarkus documentation for more details about their meaning.

$ cd camel-quarkus-mongodb-client

$ vi src/main/resources/application.properties

quarkus.package.type=uber-jar

camel.context.name = hotelsdb-client
camel.rest.component = platform-http

camel.main.routes-include-pattern = classpath:/camel-routes.xml,classpath:/camel-rests.xml


quarkus.mongodb.connection-string = mongodb://localhost:27017
quarkus.mongodb.database = hotelsdb

batchLimit = 100

quarkus.http.port = 8080
quarkus.http.host = 0.0.0.0

As you might have noticed in above configuration our business logic will be placed in two Camel files: camel-routes.xml and camel-rests.xml

First we'll edit camel-rests.xml where we'll define our REST services endpoints

$ vi src/main/resources/camel-rests.xml

<?xml version="1.0" encoding="UTF-8"?>
<rests xmlns="http://camel.apache.org/schema/spring">
    <rest id="cache" path="/camel">
         <post id="putToCache" consumes="application/json" produces="application/json" uri="/v1/cache/{cid}">
            <route>
                <doTry>
                    <to uri="direct:putToCache"/>
                    <doCatch>
                        <exception>java.lang.Exception</exception>
                        <to uri="direct:logError"/>
                    </doCatch>
                </doTry>
            </route>
         </post>
         <get id="getFromCache" produces="application/json" uri="/v1/cache/{cid}/{limit}">
            <route>
                <doTry>
                    <to uri="direct:getFromCache"/>
                    <doCatch>
                        <exception>java.lang.Exception</exception>
                        <to uri="direct:logError"/>
                    </doCatch>
                </doTry>
            </route>
        </get>
    </rest>
</rests>

This REST services endpoints will send requests to the routes which must be defined in the second camel file camel-routes.xml

$ vi src/main/resources/camel-routes.xml

<?xml version="1.0" encoding="UTF-8"?>
<routes id="DBClient" xmlns="http://camel.apache.org/schema/spring">
    <route id="Put to collection">
        <from uri="direct:putToCache"/>
        <log loggingLevel="INFO" message="Inserting to collection ${header.cid} 1 document..."/>
        <convertBodyTo type="java.lang.String"/>
        <to uri="direct:insertRecord"/>
        <log loggingLevel="INFO" message="Done"/>
        <setBody>
            <simple>{"count": 1}</simple>
        </setBody>
        <removeHeaders pattern="*"/>
    </route>
    <route id="Insert record to collection">
        <from uri="direct:insertRecord"/>
        <recipientList>
            <simple>mongodb:camelMongoClient?database={{quarkus.mongodb.database}}&amp;collection=${header.cid}&amp;operation=save</simple>
        </recipientList>
        <log loggingLevel="INFO" message="Inserted to collection ${header.cid} document with id ${header.CamelMongoOid}."/>
    </route>
    <route id="Get from collection">
        <from uri="direct:getFromCache"/>
        <validate>
            <simple>${header.limit} range '1..{{batchLimit}}'</simple>
        </validate>
        <log loggingLevel="INFO" message="Get all from cache ${header.cid} with limit ${header.limit}"/>
        <setHeader name="CamelMongoDbSortBy">
            <!--  descending by _id -->
            <constant>{"_id" : -1}</constant>
        </setHeader>
        <setHeader name="CamelMongoDbLimit">
            <simple>${header.limit}</simple>
        </setHeader>
        <setHeader name="CamelMongoDbBatchSize">
            <constant>{{batchLimit}}</constant>
        </setHeader>
        <recipientList>
            <simple>mongodb:camelMongoClient?database={{quarkus.mongodb.database}}&amp;collection=${header.cid}&amp;operation=findAll</simple>
        </recipientList>
        <to uri="direct:processOutput"/>
    </route>
    <route id="Process output">
        <from uri="direct:processOutput"/>
        <marshal>
            <json id="json" library="Jackson"/>
        </marshal>
        <removeHeaders pattern="*"/>
    </route>
    <route id="Log error">
        <from uri="direct:logError"/>
        <log logName="net.gmsworld.server.camel" loggingLevel="ERROR" message="Operation failed with exception: ${exception.stacktrace}"/>
        <setBody>
            <simple>{"error" : "Operation failed"}</simple>
        </setBody>
        <removeHeaders pattern="*"/>
        <setHeader name="CamelHttpResponseCode">
            <constant>500</constant>
        </setHeader>
    </route>
</routes>

Finally we are going to modify the automatically generated JUnit test case source file. Of course you can create your own JUnit test cases just like with a regular Java applications.

$ vi src/test/java/org/redhat/GreetingResourceTest.java

package org.redhat;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class GreetingResourceTest {

    @Test
    public void testHelloEndpoint() {
        given()
          .when().get("/camel/v1/cache/test/10")
          .then()
             .statusCode(200)
             .body(is("[]"));
    }
}

Now we are ready for testing!

For testing purposes let's run on the local machine a MongoDB database container. Moving forward I'll use podman for all container related actions, but if you prefer you can use any other OCI compliant tool.

$ podman run -d --name mongodb -p 27017:27017 quay.io/bitnami/mongodb:4.0

Before proceeding make sure MongoDB container is up and running

$ podman ps
CONTAINER ID  IMAGE                        COMMAND               CREATED      STATUS            PORTS                     NAMES
780b48bfd200  quay.io/bitnami/mongodb:4.0  /opt/bitnami/scri...  11 days ago  Up 2 seconds ago  0.0.0.0:27017->27017/tcp  mongodb

Now we can quickly execute JUnit test

$ mvn clean package

If it has succeeded we can start our REST service in developer mode on the local machine using maven Quarkus plugin

$ mvn clean package quarkus:dev -DskipTests=true

Once our service is up and running we can send some requests to test it

$ curl -v -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}'  http://localhost:8080/camel/v1/cache/test

$ curl -v http://localhost:8080/camel/v1/cache/test/10

Next we will compile or Camel Quarkus application to native executable using GraalVM

First we need to prepare additional configuration for native compiler to make sure required XML dsl files and Java classes are included in the output native executable.

vi src/main/resources/application.properties

#add following parameter
quarkus.native.additional-build-args =\
   -H:ResourceConfigurationFiles=resources-config.json,\
   -H:ReflectionConfigurationFiles=reflection-config.json

vi src/main/resources/resources-config.json

{
  "resources": [
    {
      "pattern": ".*\\.xml$"
    }
  ]
}

vi src/main/resources/reflection-config.json

[
  {
    "name" : "org.bson.types.ObjectId",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true
  },
  {
    "name" : "java.lang.Exception",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true
  } 
]

In order to create native executable you can either download to your local machine GraalVM and execute following maven command

$ mvn clean package -Pnative -DskipTests=true -DGRAALVM_HOME=/opt/graalvm/graalvm-ce-java11-21.0.0.2/

or you can run containerized native executable builder

$ mvn package -Pnative \
-Dquarkus.native.container-build=true \
-Dquarkus.native.container-runtime=podman \
-Dquarkus.native.builder-image=registry.access.redhat.com/quarkus/mandrel-20-rhel8

Both commands should produce native executable which can be optionally analysed using following command

$ readelf -h ./target/camel-quarkus-mongodb-client-1.0.0-SNAPSHOT-runner
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64

 

Now you can run this binary and test it the same way as before. Please note this native executable will start REST service in just a couple of milliseconds

$ ./target/camel-quarkus-mongodb-client-1.0.0-SNAPSHOT-runner

2021-08-16 13:12:01,922 INFO  [io.quarkus] (main) camel-quarkus-mongodb-client 1.0.0-SNAPSHOT native (powered by Quarkus 1.13.7.Final) started in 0.059s. Listening on: http://0.0.0.0:8080
2021-08-16 13:12:01,922 INFO  [io.quarkus] (main) Profile prod activated.
2021-08-16 13:12:01,922 INFO  [io.quarkus] (main) Installed features: [camel-attachments, camel-bean, camel-core, camel-direct, camel-http, camel-jackson, camel-log, camel-mongodb, camel-platform-http, camel-rest, camel-support-common, camel-support-commons-logging, camel-support-httpclient, camel-support-mongodb, camel-xml-io-dsl, cdi, mongodb-client, mutiny, resteasy, smallrye-context-propagation, vertx, vertx-web]

If you are curious you can also check how much memory is consumed by the native executable

$ ps -o pid,rss,command -p $(pgrep -f runner)
  PID   RSS COMMAND
 2575 61956 ./target/camel-quarkus-mongodb-client-1.0.0-SNAPSHOT-runner

Less than 62MB, pretty compact compared to a regular JVM application!

Let's repeat the test executed before to make sure our Camel Quarkus application is up and running

$ curl -v -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}'  http://localhost:8080/camel/v1/cache/test

$ curl -v http://localhost:8080/camel/v1/cache/test/10

Finally, let's create a lightweight container image. For that I'll use ubi-micro base image extended by required for Quarkus native libraries created using this script. To execute this script on your local machine you'll need yet another tool for building images called buildah.

$ vi Containerfile.distroless

FROM quay.io/jstakun/ubi-micro-quarkus:latest
MAINTAINER Jaroslaw Stakun jstakun@redhat.com
LABEL quarkus-version=1.13.7.Final
COPY ./target/*-runner /application
RUN chgrp 0 /application && chmod 110 /application
USER 1001
CMD /application
EXPOSE 8080

$ podman build -f ./Containerfile.distroless -t quay.io/jstakun/camel-quarkus-mongodb-client:latest

$ podman push quay.io/jstakun/camel-quarkus-mongodb-client:latest

With the image deployed to the public container images registry you can run the container anywhere you want. Here is how you can do it in Red Hat OpenShift Container Platform.

$ oc new-project camel-quarkus-mongodb

$ oc new-app -e MONGODB_DATABASE=testdb -e MONGODB_USER=test -e MONGODB_PASSWORD=test -e MONGODB_ADMIN_PASSWORD=admin mongodb:3.6
  

#please note using environment variables you can replace values of application properties defined in source code
$ oc new-app --name=frontend -e QUARKUS_MONGODB_CONNECTION_STRING=mongodb://mongodb:27017 -e QUARKUS_MONGODB_DATABASE=testdb  -e QUARKUS_MONGODB_CREDENTIALS_USERNAME=test -e QUARKUS_MONGODB_CREDENTIALS_PASSWORD=test quay.io/jstakun/camel-quarkus-mongodb-client:latest

$ oc expose service/frontend

Finally let's test our REST service pod running in OpenShift

$ ROUTE=http://$(oc get route | grep frontend | awk '{print $2}') && echo $ROUTE

$ curl -v -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' $ROUTE/camel/v1/cache/test

$ curl -v $ROUTE/camel/v1/cache/test/10
 

You can find all source codes of this tutorial in my GitHub repository. All container images referenced in this post are available at the quay.io public container images registry.

Thanks for reading!

środa, 5 maja 2021

Optimize Kubernetes/OpenShift clusters resources utilization

It is quite common situation when your OpenShift or Kubernetes cluster resources utilization is very low but despite this at some point you are unable to deploy new pods which remains in pending state due to insufficient resources error. It could be even more frustrating if number of pods running on your cluster nodes is significantly below limit of 500 pods/node in OpenShift or 100 pods/node in upstream Kubernetes.


 

In most cases solution will be proper assignment of hardware resources to pods in your cluster. First you should analyze whether your cluster has pod deployments which are significantly over estimated. In OpenShift you can leverage built in monitoring which will show you instantly whether your cluster in over estimated and which pod deployments are over estimated: 

 


 

Let's try to analyze where this numbers comes from. Utilization/Usage values are actual utilization of hardware resources by running pods, and Request Commitment values are based on what has been set as resource request value in pod deployment definition or is set in default settings in Limit Range object. It is very important to understand that requested resources are reserved by the Kubernetes Scheduler exclusively for the pod and if not consumed will be wasted. Hence difference between actual utilization and requested resource value should be as small as possible to minimize the waste of hardware resources. 

This leads to the next question: how to calculate optimal resource request value for pod deployment? Of course the best way would be to load test your application for longer time and come up with the most accurate value. There is a great tool  Vertical Pod Autoscaler (VPA) which can calculate for you recommended pod deployment resource request and limit values based on historical resources consumption. VPA can even automatically apply recommendations to running pods but I don't recommended this for production clusters.  

Moving forward lets see how pods scaling might impact resources utilization. Generally you have two ways to scale your pods:

1. Scale up pods by setting in pod deployment resource limit to higher value than resource request. This might lead to node overcommit situation when amount of resource limits set in pods is higher than amount of resources available on the node. This could impact negatively node stability when resources utilization demand will grow above what is available on the node. If you want to allow node overcommit on your cluster you should always remember to reserve some resources for node system processes and define pod disruption budget and pod priorites to hint Scheduler which pods are running critical workloads. I don't recommend to allow resource over commit on production clusters.

2. Scale out using Horizontal Pod Autoscaler (HPA). Using HPA you can configure pod auto scaling based on selected metrics i.e. CPU or memory utilization percentage compared to configured in pod deployment resource request. Hence again it is very important to configure resource request to optimal value as described above.

Especially for production workloads I recommend to implement scale out strategy for pods scaling. 

Other important factor related to resources utilization is node workload balancing. Over time due to different reasons some nodes might become significantly more utilized than the others. This might negatively impact overall cluster capacity. In order to solve this challenge you can deploy to your cluster Node Descheduler (ND). Using ND you can enable different profiles which will detect different pods deployment patterns:

1. AffinityAndTaints: This profile evicts pods that violate inter-pod anti-affinity, node affinity, and node taints.

2. TopologyAndDuplicates: This profile evicts pods in an effort to evenly spread similar pods, or pods of the same topology domain, among nodes.

3. LifecycleAndUtilization: This profile evicts long-running pods and balances resource usage between nodes.

I expect over time more advanced deschedulers will be created based on more advanced patterns that will utilize AI/ML for optimal resources utilization and balancing. I recommend to combine ND with Cluster Autoscaler (CA). The cluster autoscaler increases the size of the cluster when there are pods that fails to schedule on cluster nodes due to insufficient resources or when another node is necessary to meet deployment needs.

So far I've shortly described cluster level tools to optimize resources utilization but almost always there is a huge space for improvement in the applications itself. Using cloud native architectures like microservices, cloud native and containers optimized frameworks like Quarkus (especially in Native Mode) and advanced deployment patterns like Serverless for automated scaling will also help to significantly optimize cluster resources utilization. This is great topic for other post...

All aforementioned optimizations will require some time and effort. It is good to be able to estimate financial costs and savings of these optimizations in advance. In OpenShift we have created very handy Cost Management dashboard which is available for connected clusters in Red Hat Cloud
 



środa, 3 marca 2021

Detect failed login events in Openshift 4

In this blog post I'll try to answer the question how to detect failed login attempts in OpenShift 4. Typically you'll look for two major pieces of information to analyze the failed login events: user login being used and request source IP. 

There are a number of authentication flows available in OpenShift 4. In this post I'll focus on three most common: web console authentication using identity provider credentials, oc command line interface authentication using identity provider credentials and oc command line interface bearer token authentication.

1. OpenShift web console authentication flow

User authentication is performed by oauth-openshift pods located in openshift-authentication project. In order to log failed login attempts first you must change log level to Debug for these pods in authentication operator crd:

oc edit authentications.operator.openshift.io
...
spec:
  logLevel: Debug
... 

 
This should let you observe following logs whenever failed login attempt will occur:
 
oc -n openshift-authentication logs deployment.apps/oauth-openshift
 
I0303 09:44:55.129099 1 login.go:177] Login with provider "htpasswd_provider" failed for "webhacker"

This entry won't give you information about the source IP of failed login request hence you must configure ingress access logging following the documentation or simply use following command:

oc edit ingresscontroller -n openshift-ingress-operator
...
spec:
  logging:
access:
destination:
type: Container
...  

Once it is done you can search for following entries in the router access log to identify failed authentication requests source IP: 

oc -n openshift-ingress logs deployment.apps/router-default -c logs

2021-03-03T09:44:55.609880+00:00 router-default-6fd8d9cdc7-qn8b5 router-default-6fd8d9cdc7-qn8b5 haproxy[204]: 39.68.32.67:47621 [03/Mar/2021:09:44:42.604] fe_sni~ be_secure:openshift-console:console/pod:console-8f4c45fb5-hqh4w:console:https:10.129.0.20:8443 0/0/0/5/5 303 931 - - --VN 88/43/36/21/0 0/0 "GET /auth/login HTTP/1.1"

I recommend leveraging the built in OpenShift Logging stack to easily discover these logs using built in Kibana UI. Remember first to create infra index in Kibana UI and then you can use following filters to discover above mentioned log entries:

kubernetes.namespace_name:openshift-authentication AND message:"*failed for*"


kubernetes.namespace_name:openshift-ingress AND kubernetes.container_name:logs AND message:"*/auth/login*"


  2. OC
command line interface authentication flow

In this flow authentication is also performed by oauth-openshift pods located in openshift-authentication project the same way as in web console authentication flow hence you should search for the same failed login attempts logs as above:

oc -n openshift-authentication logs deployment.apps/oauth-openshift
 
I0303 10:31:58.977373 1 basicauth.go:50] Login with provider "htpasswd_provider" failed for login "clihacker"

Tricky part is to discover the source IP of the request. This time you'll need to look for access log entries sent from haproxy public_ssl frontend to oauth-openshift backend pod at the time of failed login attempt:

oc -n openshift-ingress logs deployment.apps/router-default -c logs

2021-03-03T10:31:59.057027+00:00 router-default-6fd8d9cdc7-v49zg router-default-6fd8d9cdc7-v49zg haproxy[176]: 39.68.32.67:47771 [03/Mar/2021:10:31:57.507] public_ssl be_tcp:openshift-authentication:oauth-openshift/pod:oauth-openshift-586c689c67-2vggm:oauth-openshift:https:10.128.0.25:6443 4/1/1549 4139 -- 4/3/2/0/0 0/0

Due to fact login requests in this flow are passed encrypted directly to oauth-openshift pod haproxy ingress controller will only log tcp log entries instead of full http log entries. 

If you are using Kibana UI you might use the same filter as before:


kubernetes.namespace_name:openshift-authentication AND message:"*failed for*"

 

In Kibana UI you can easily check what exactly oauth-openshift pod was handling the request by checking kubernetes.pod_name field and generate query filter:

#paste kubernetes.pod_name as POD value
POD=oauth-openshift-586c689c67-2vggm
IP=$(oc get pod/$POD -n openshift-authentication -o template --template '{{.status.podIP}}')

#generate query filter for Kibana
echo kubernetes.namespace_name:openshift-ingress AND kubernetes.container_name:logs AND message:"*public_ssl be_tcp:openshift-authentication:oauth-openshift/pod:$POD:oauth-openshift:https:$IP:6443*"

Copy and paste this query filter to Kibana UI


3. OC command line interface authentication using bearer token flow

This authentication flow is completely different from the two above. Authentication request is handled directly by API endpoint and not oauth authentication pods as before. API endpoint audit log is up and running by default in OpenShift 4 and you can access it as per the documentation

I found it complex to analyze API audit logs this way, hence I decided to leverage the built in OpenShift Logging stack again. First in order to aggregate API audit logs using built in OpenShift Logging stack you should create following log forwarder configuration:

echo "---
apiVersion: logging.openshift.io/v1
kind: ClusterLogForwarder
metadata:
  namespace: openshift-logging
  name: instance
spec:
  pipelines:
    - name: enable-default-log-store
      inputRefs:
        - application
        - infrastructure
        - audit
      outputRefs:
        - default
" | oc create -f - -n openshift-logging

Next you should create audit index in Kibana UI and you should search for failed login attempts using following filter:

requestURI:"/apis/user.openshift.io/v1/users/~" AND responseStatus.code:401


API audit log contains source IP which should show you where the failed login request comes from and of course there is no user login as the bearer token was not resolved to any existing OpenShift user identity. 

Remember in OpenShift API requests will arrive via load balancer hence the load balancer should retain client ip when proxying requests to API server.

If you would like to automate detection of failed logins in your OpenShift clusters you should leverage 3rd party solutions which can automatically filter logs described above. One option you can give a try is my elastalert-ocp project, however it has some drawbacks as described in README file.