ICP to OpenShift migration guide

This document is developed by IBM garage consultants to provide reference guide when migrating IBM Cloud Private (ICP) to Red Hat OpenShift. It is maintained primarily by IBM Garage Solutoin Engineering team to assit in service delivery, but the guide is open to wider community in the spirit of sharing.

The guide is NOT official product documentation. It is still being developed and updates are ongoing.

We welcome contributions from the community by opening an issue or submit a pull request. Please reference the Contribution guide for detail.

Key differences between ICP and OpenShift

IBM Cloud Private (ICP) and Red Hat OpenShift are different Kubernetes distributions. Fundamentally, both are based on the core Kubernetes technologies. Thus, they bring relatively consistent experience for application development and platform operation. From migration perspective, we’ll focus on the key differences between the two platforms.

The following is the summary of the key differences:

ICP OpenShift Migration Effort

Infrastructure

Hardware

x86_64, Power(ppc64le), IBM Z

X86_64, Power, IBM Z

Installation on Bare-metal (including IBM Z) requires medium effort

Operating System

Red Hat Enterprise Linux 7.3, 7.4, 7.5; Ubuntu 18.04 LTS and 16.04 LTS; SUSE Linux Enterprise (SLES) 12

RHEL Red Hat CoreOS (RHCOS)

Migrating from Ubuntu / SUSE to OpenShift, customer may require operation procedure change such as OS patching, security certification etc.

IaaS provider

VMWare/OpenStack Most public cloud IaaS providers

VMWare/OpenStack/zVM Most of the public cloud IaaS providerIBM Cloud and Azure provide managed OpenShift service

Need to pay attention on the networking and storage for the specific IaaS provider, as well as any automation code specific to an IaaS provider

HA Cluster Topology

Master Proxy Management VA

Master Worker

Migration effort should be part of the OpenShift planning and installation

Installation

Ansible installer Delivered as Docker image

Ansible installer for OCP 3.x, v4.x completely changed the installation procedure with Operator and CoreOS.

Installation procedure particularly any automation script requires change

Container Registry

ICP private registry, External Container Registry

OpenShift Container Registry (OCR), Quay, External Container Registry

Small effort

Kubernetes Version

Kubernetes is generally stable after 1.9 but there are some features (e.g. storage-related) that are beta in 1.11

Development

Local dev environment

Vagrant based local ICP cluster with ICP Community edition

Minishift All-in-one, OKD

Small effort

Development Layer

Projects

Provides higher level Kubernetes construct simplifying deployments.

Development tools

Standard Kubernetes Microprofile

Standard Kubernetes Source-to-image (S2I) fabric8

Not much needs to be done for developers

DevOps

Platform neutral DevOps toolchain

Packaged Jenkins for CI Image Stream for container images2i

Small to Medium effort.

Application package

Standard Kubernetes yaml Helm Operator

Standard Kubernetes yaml, OpenShift template OpenShift Application, Operator, Helm Operator

Medium to large effort

Deployment

Standard Kubernetes deployment and strategy

OpenShift opinionated Deployment Config and ImageStream

Suggest to keep the Kubernetes standard approach. Small effort

Operation

Command Line Tool

kubectl

oc (superset of kubetl) kubectl

Small effort when cli is used any operation or devops automation

User interface

ICP UI

OpenShift UI

Small effort

Multi-tenancy

Standard Kubernetes namespace and RBAC

OpenShift Project with RBAC Operator can apply Quotas and limit per project or cluster

Medium effort

Networking

SDN (Software Defined Network)

Default on Calico Support other Kubernetes supported SDN

Default to Red Hat Open vSwitch SDN, Can use other Kubernetes supported SDN as well

Small effort. Most of these are internal to ICP and OpenShift

Cluster DNS

CoreDNS

SkyDNS (3.x) CoreDNS (4.x)

External Access for Services

Ingress Controller Load Balancer

Router (HAProxy) Load Balancer

Medium effort to update the service exposure

Storage

File Storage

GlusterFS NFS

GlusterFS NFS Ceph

Security

Container permission

Allows to run container as root

Forbids to run a container as root by default (best practice)

Small effort to rebuild the application container

Authentication

OpenID with primarily LDAP identify provider

OAuth with identity provider OpenShift supports different kinds of IAM

Small effort

Authorization

RBAC as above Kubernetes called Pod Security Policies (PSP) beta

RBAC as above OpenShift Security Context Constraint (SCC)

Small effort, some changes needed in particular to address the SCC

Securing the master

TLS to master X.509 certifate or token to access API server

TLSX.509 certifate or token to access API server Project quota to limit the token rate

Small effort

Development Experience

OpenShift Development Environment

The goal of OpenShift is to provide a great experience for both Developers and System Administrators to develop, deploy, and run containerized applications. Developers should love using OpenShift because it enables them to take advantage of both containerized applications and orchestration without having to know the details. Developers are free to focus on their code instead of spending time writing Dockerfiles and running docker builds.

OpenShift is a full platform that incorporates several upstream projects while also providing additional features and functionality to make those upstream projects easier to consume. The core of the platform is containers and orchestration. For the container side of the house, the platform uses images based upon the docker image format. For the orchestration side, it is based on upstream Kubernetes project. Beyond these two upstream projects, there are a set of additional Kubernetes objects such as routes and deployment configs.

Standard Interfaces Differences (oc tool usage vs. kubectl and HELM)

Both Developers and Operators communicate with the OpenShift Platform via one of the following methods:

  • Command Line Interface: The command line tool that we will be using as part of this training is called the oc tool. This tool is written in the Go programming language and is a single executable that is provided for Windows, OS X, and the Linux Operating Systems.

  • A Web Console: User friendly graphical interface

  • REST API: Both the command line tool and the web console actually communicate to OpenShift via the same method, the REST API. Having a robust API allows users to create their own scripts and automation depending on their specific requirements. For detailed information about the REST API, check out the official documentation at: https://docs.openshift.org/latest/rest_api/index.html[https://docs.OpenShift.org/latest/rest_api/index.html]

IBM Cloud Private also provides a CLI. Many interactions with ICP though happen through the standard Kubernetes CLI called kubectl. Developers also made use of HELM as a package manager to deploy workloads. Whereas the pattern for ICP developers was to make heavy use of kubectl or HELM to deploy workloads and applications, OpenShift users often make more use of the oc commandline tool than kubectl. (Note: HELM can be used in OpenShift environment but it must be installed into OpenShift. IBM Cloud Paks provide this ability as a core service over OpenShift).

OpenShift aims to greatly simplify development and deployment of applications, thus providing a layer over Containers (much like a Cloud Foundry would), and the oc tool provides those tools.

Projects

OpenShift is often referred to as a container application platform in that it is a platform designed for the development and deployment of containers.

To contain your application, OpenShift use projects. The reason for having a project to contain your application is to allow for controlled access and quotas for developers or teams. More technically, it’s a visualization of the Kubernetes namespace based on the developer access controls. Under the hood, while project'' is a separate object returned by the OpenShift API, there is a one-to-one mapping between projects'' and ``namespaces'' in Kubernetes.

The typical experience goes something like:

  • Developer logs in to the console or CLI and creates a project

  • Add artifacts to project. This can take several forms, for example

    • Deploy an existing Image (usually Docker based) and with optionally additional YAML files.

    • Create an application out of templates.

    • Create pipelines out of several approaches. (OpenShift has a built in mechanism called Source 2 Image, of s2i that can deploy straight from a git repository)

  • Configure resources.

    • Items include exposing a Route (Described later in the article)

    • Scale Pods.

When you create a Project and add a deployment, several of the Kubernetes Objects are created for you by default. This includes:

  • Pods: Where your containers run which you can begin to scale immediately.

  • Services: provide internal abstraction and load balancing within an OpenShift environment, but sometimes clients (users, systems, devices, etc.) outside of OpenShift need to access an application.

  • Routes: The way that external clients are able to access applications running in OpenShift. (Similar to Ingress or Node Ports).

A great way to get started with the development experience is through the following website. https://learn.OpenShift.com/

Migration of applications from ICP to OpenShift.

There are actually many paths you can take to do this.

Development Environments

OpenShift developers can use several approaches to local development.

  • Develop code and Docker images locally and deploy to a remote cluster. There are several ``managed OpenShift Options'' on various public clouds.

  • If you need to run a local kubernetes distribution you can use.

    • Minikube: This is the standard community Kubernetes. However, this will require you maintain duplicate YAML artifacts. This approach is not recommended.

    • OKD: This is the Origin Community Distribution that powers OpenShift. You can access it here: https://www.okd.io/https://www.okd.io/]. OKD provides a feature complete version of OpenShift.

    • Minishift is a tool that helps you run OKD locally by launching a single-node OKD cluster inside a virtual machine. With Minishift you can try out OKD or develop with it, day-to-day, on your local machine. You can run Minishift on the Windows, macOS, and GNU/Linux operating systems. More information can be found here: https://www.okd.io/minishift/

    • CodeReady Containers: Starting with OpenShift 4, CodeReady Containers is the standard way to run a local OpenShift environment. Red Hat CodeReady Containers brings a minimal OpenShift 4.0 or newer cluster to your local computer. This cluster provides a minimal environment for development and testing purposes. It’s mainly targeted at running on developers' desktops.

OpenShift is not opinionated on the application stack and provides templates for various popular OpenSource frameworks such as Spring, Java EE, JBoss, Quarkus, Node, etc…. A great place to learn about various types of applications you can build is here: https://learn.OpenShift.com/middleware/

Additional tools, CLI’s, and Frameworks

In addition to the oc tool, there are several more CLI’s, tools, and frameworks that you should be aware of.

  • odo: a CLI tool for developers who are writing, building, and deploying applications on OpenShift. With odo, developers get an opinionated CLI tool that supports fast, iterative development. odo abstracts away Kubernetes and OpenShift concepts so developers can focus on what’s most important to them: code. odo was created to improve the developer experience with OpenShift. Existing tools such as oc are more operations-focused and require a deep understanding of Kubernetes and OpenShift concepts. More information can be found here: https://OpenShiftdo.org/

  • Source-to-Image (S2I): Source-to-Image (S2I) is a toolkit and workflow for building reproducible container images from source code. It is worth noting that you can use any CI / CD tool with OpenShift as well. More information can be found here: https://github.com/OpenShift/source-to-image. We will discuss this more in the next section.

  • CodeReady: Built on the open Eclipse Che project, Red Hat CodeReady Workspaces provides developer workspaces, which include all the tools and the dependencies that are needed to code, build, test, run, and debug applications. More information can be found here: https://developers.redhat.com/products/codeready-workspaces/overview

OpenShift developers can also use popular projects such as ISTIO, kNative, and others on the platform

  • ISTIO is a service mesh that provides features such as routing, secure communication, Circuit Breaker, and Application diagnostic tools. Istio is supported throught he OpenShift Service Mesh offering, which is a Tech Preview and will be GA at the end of Aug 2019. To learn how to use ISTIO on OpenShift, go here: https://learn.OpenShift.com/servicemesh/

  • Knative extends Kubernetes to provide components for building, deploying, and managing serverless applications

  • Tekton is a cloud-native CI/CD framework where pipeline stages are executed in containers. Tekton is part of the OpenShift Pipelines offering. For more information go here: https://blog.OpenShift.com/cloud-native-ci-cd-with-OpenShift-pipelines/

  • Operators are a framework for building Kubernetes-native applications. Red Hat provides and SDK for getting up and running on creating Operators from Helm charts, Ansible playbooks, and go code. For more information see: https://github.com/operator-framework/getting-started

IBM Cloud Pak for Applications and additional Open Source projects

IBM announced the Cloud Pak for Applications which includes support for IBM application runtimes such as IBM WebSphere Liberty and middleware such as IBM MobileFirst Foundation

It also includes various recently-announced open source projects maintained by IBM around developer tooling. These include:

The IBM Cloud Pak for Applications is still in development and may include more components in the future.

DevOps

As mentioned earlier, OpenShift provides an opinionated development platform around source-to-image (S2I) as a differentiator over upstream community Kubernetes. As a comparison to ICP, it was not opinionated on DevOps beyond providing (outdated) community Helm Charts for Jenkins. S2I is an integrated build and deployment framework that developers can use to run code in containers in the platform without additional infrastructure.

Note that if DevOps procedures are already mature and not tied to the platform, and infrastructure is outside of the platform, it’s possible to reuse most of it as OpenShift conforms to Kubernetes. There are some minor differences around security which are discussed later in this document.

That said, a large part of OpenShift value proposition is that it’s an integrated development platform in addition to being a container orchestrator. OpenShift includes some CustomResourceDefinitions (CRDs) around continuous integration (CI) and continuous deployment (CD) that enhance developer productivity. As the controllers for these objects are built-in to the OpenShift API, they are not portable outside of OpenShift.

ImageStream

An ImageStream represents an image either in the internal OpenShift container image registry, or in an external registry. An image in an external registry can be mirrored and cached in the local container image registry.

There are a few related resources to ImageStreams:

  • The ImageStream resource represents the repository part of the image

  • The ImageStreamTag resource represents an individual tag, which points at the hash of the image as stored in the registry. This hash is immutable and every push to the tag will update the hash, assuming the image has changes.

For example, if we were to import docker.io/ibmcom/websphere-liberty:latest, the ImageStream part would be docker.io/ibmcom/websphere-liberty'', and the tag would be latest''. The ImageStreamTag would represent the pointer to the image represented by ``docker.io/ibmcom/websphere-liberty:latest'', which changes every time someone pushes to the ibmcom/websphere-liberty:latest tag.

OpenShift will deploy the image hash in deployments and the ImageStreamTag tracks the upstream images as they change. As such, we can use ImageStreams to track changes to images even if the image in the original tag changes.

Images in external registries can be imported into OpenShift as ImageStreams, and mirrored on a schedule. ImageStream changes can trigger builds or redeployments; this can be useful in cases such as triggering rebuilds on a nightly patched image updates for base images, or as part of a continuous deployment procedure where image tags are used to track image deployments to certain environments.

Additionally, since the ImageStream objects are stored in OpenShift/Kubernetes, RBAC can be applied to them and they can be scoped to individual projects or shared to multiple projects. This is similar to how ICP manages RBAC around images as well in its private registry.

View the FAQ on the ImageStream here: https://blog.OpenShift.com/image-streams-faq/

BuildConfig

For Continuous Integration, the BuildConfig is a CustomResource is used to produce a target image based on inputs and triggers. The BuildConfig takes as input:

  • Source code (such as a git repository) or binaries, (for example, a directory as part of an external pipeline)

  • Source ImageStream (for example a base image like ibmcom/websphere-liberty)

  • Target ImageStream which contains the built application artifact

    There are various strategies around BuildConfig, which control how the target image stream is assembled:

  • Source strategy: this is the core of S2I where a builder image is provided that builds the source and packages it into a target container image, then pushes it into the OpenShift private registry. This requires the builder image to have knowledge about how to turn code into a container image. For example, for Java code, the builder image may run ``mvn package'', take the output binaries and build an image from a Java runtime. Red Hat ships several builder images for popular runtimes, but any custom runtimes or deviations from the happy path may require additional work to support. Red Hat provides an SDK/documentation on how to build custom builder images here: https://github.com/OpenShift/source-to-image

  • Docker strategy: this is equivalent to running docker build'' on a local machine, except it is done through OpenShift. As part of this, the context directory and a Dockerfile are uploaded to OpenShift where it the container image is assembled from binaries. There are advantages to this, mainly that in some CI scenarios in multi-tenant environments where the administrators do not want to expose docker socket for direct docker build'', as this exposes root access on the machine where the container is assembled.

  • Pipeline strategy: this is equivalent to creating a staged build pipeline through Jenkins. In this BuildConfig type, an embedded Jenkins declarative pipeline is defined in the body of the resource. OpenShift will provision an instance of Jenkins in the project to execute the build and will sync the build status from Jenkins to the Build object (more on it below). The OpenShift Application console contains some UI elements that show the build status from Jenkins.

An instance of an execution of BuildConfig is a Build. Builds can be triggered when the upstream source is changed, when the source ImageStream changes, or manually using "oc new-build". An execution of BuildConfig results in a new Build object being created, which has a build number that increments every time the build is run. BuildConfig can maintain build history for both successful and unsuccessful builds. The build itself is run in a build pod.

DeploymentConfig

OpenShift has DeploymentConfigs, which is a precursor to the Kubernetes Deployments. The DeploymentConfig resource is not portable to non-OpenShift Kubernetes distributions. Note that OpenShift also supports the familiar Deployment resource as well, so in terms of moving from ICP or other Kubernetes distributions, offers basically zero migration effort and is more community-friendly.

DeploymentConfig does provide deeper integration with ImageStreams, in that when an ImageStream is updated, OpenShift can perform an update of the Deployment. OpenShift can also extend this integration with ImageStreams to regular Deployments by configuration, see https://docs.OpenShift.com/container-platform/3.11/dev_guide/managing_images.html#using-is-with-k8s.

Additionally, DeploymentConfig supports a few advanced deployment strategies, which are detailed here: https://docs.OpenShift.com/container-platform/3.11/dev_guide/deployments/deployment_strategies.html. Most notably, they claim support for ``canary'' deployments, although the documentation suggests the regular rolling update is a form of canary deployment (which it isn’t, as the deployment continues to get rolled over as soon as the health checks pass). There is also support for A/B testing and blue-green deployments.

There are additional features and differences between Deployments and DeploymentConfigs in OpenShift. When a DeploymentConfig rolls out a deployment, a deploy'' pod is created that performs the actual deployment, as opposed to a controller running on the master performing the rollout. This may be slightly more scalable in very large clusters where many rolling deployments are happening simultaneously. Additionally, rollouts may be paused and resumed as needed. Also, a handy command is the oc rollout latest'', which just re-deploys the same version of the pod; this is useful if a ConfigMap has changed and the pods need to restart to refresh them.

Templates

OpenShift provides support for Template resources, which are regular OpenShift objects with parametrized fields in them. This is similar to Helm template, but without the advanced ability to generate random data, conditionals, or complex variable types.

The ``oc process'' command is used to convert a template to a regular resource. The Template is a list of one or more templated resources, and can be stored in the OpenShift API for re-use, or processed from local filesystem. Templates form the base for the "oc new-app" command which generates a list of resources from a list of parameters.

Again, as templates are very OpenShift specific, use discretion before using. There are several other open-source Kubernetes templating projects, for example Helm and Kustomize, that are more portable and more community-friendly. Generally Red Hat frowns upon Helm 2.x as server side tiller requires large permissions and the helm client requires read access to the namespace where tiller runs; Helm 3 addresses this by including tiller on client side.

Infrastructure

This chapter explores the infrastructure consideration when migrating from ICP to OpenShift. It covers the hardware platform, IaaS and hypervisors, operating system and platform automation.

Hardware and hypervisor

ICP can be deployed on (Linux) x86_64, Power (ppc64le) and IBM Z and LinuxOne. OpenShift now can run x86_64, Power and IBM Z hardware. Each has its own sizing recommendation in terms of CPU, memory and disk space. You can reference the system requirement for both below:

Both ICP and OpenShift can run on Hypervisors like VMware, OpenStack and Hyper-V in a private cloud environment. ICP is also supported on IBM PowerVC.

IaaS

Both ICP and OpenShift can run on public or private IaaS. In public. We have tested ICP on IBM Cloud, Azure, AWS, GCP, and Huawei Cloud. On the other hand, we have tested OpenShift on IBM Cloud, Azure, AWS.

For OpenShift on public cloud, there are potentially 3 offering:

  • Managed OpenShift cluster. This includes IBM IKS managed OpenShift (beta) and Azure Managed OpenShift

  • Guided-provision OpenShift cluster. The IaaS vendors provide guided automation procedure to provision a full OpenShift cluster either through UI or automation scripts. For example, Azure OpenShift cluster and AWS OpenShift quickstart.

  • Build your own cluster. End user provisions IaaS VMs (or bare metal), then install OpenShift on top of the VMs.

ICP doesn’t have a managed edition.

Operating System

This is where you should pay the most attention when migrating from ICP.

Both platforms can only run on top of Linux OS. ICP supports Red Hat Enterprise Linux (RHEL) 7.3, 7.4 and 7.5, Ubuntu 18.04 LTS and 16.04 LTS, SUSE Linux Enterprise (SLES) 12. While OpenShift supports only RHEL 7.4 or later in 3.x, or Red Hat Enterprise Linux CoreOS (RHCOS) in release 4.x. In OpenShift Container Platform 4.1, you must use RHCOS for all masters, but you can use Red Hat Enterprise Linux (RHEL) as the operating system for compute, or worker, machines. If you choose to use RHEL workers, you must perform more system maintenance than if you use RHCOS for all of the cluster machines.

What does this mean is that you need to switch RHEL or RHCOS when migrating ICP running on Ubuntu or Suse Linux. Most of this is infrastructure related Ops activity.

Storage

Work in progress.

Security

SELinux

OpenShift requires SELinux to be enforcing'' and targeted'' mode. When containers are run, the container image’s filesystem is labeled using a random label and the container processes are labeled the same way, so that only the container processes can access its own filesystem and no other processes. Any mounted filesystems (secrets, configmaps, or volumes) will have an SELinux policy applied to them to allow the container to read and write to them.

PodSecurityPolicy vs SecurityContextConstraints

OpenShift SecurityContextContsraints (SCC) is the pre-cursor to the PodSecurityPolicy (PSP) in upstream community Kubernetes. As such, a lot of the properties of the PSP come directly from the SCC. These objects are cluster-scoped policies designed to limit the access of containers to the host kernel. Most containers do not need to privileged access to the host and should as a best practice not depend on the uid of the user owning the container process. However, many containers on DockerHub and even some IBM middleware require running as root or some other capabilities in order to function.

One important thing to note is that while the PodSecurityPolicy objects can be created in OpenShift, the platform will ignore these objects and only enforces the SecurityContextConstraints objects. OpenShift ships with some out of the box SCCs, the default restricted'' policy is the most restrictive, and the privileged'' policy is the most open.

One very large difference is that the default policy in OpenShift will generate random a uid/gid from a range for the container process to run as (the restricted'' policy), and if your container depends on a specific uid/gid being set, the container may not run. One common example is if container requires reads or writes to the local filesystem as a specific user. In this case, the nonroot'' SCC seems to match the ``ibm-restricted-psp'' default policy that ICP ships with.

Here is a comparison of the out-of-box SCCs to those shipped with ICP, as well as some brief comments:

OpenShift ICP Comments

anyuid

ibm-anyuid-psp

Container is allowed to run as any uid, including root, but within restricted SELinux context

hostaccess

(n/a)

Container is allowed to access host namespaces (i.e. can mount filesystem and network of the host), but must run as random non-root user

(n/a)

ibm-anyuid-hostaccess-psp

Container is allowed to access host namespaces (i.e. can mount filesystem, access host network, and access any other namespaced resources on the host), and may run as any user

hostmount-anyuid

ibm-anyuid-hostpath-psp

Container is allowed to run as any user and can mount host directories

hostnetwork

(n/a)

Container can run on the host network, but must run as random selected non-root user

nonroot

ibm-restricted-psp

Container can run as any user except root; this is useful for containers that expect to run as a particular UID from its local /etc/passwd

privileged

ibm-privileged-psp

Run as any user and have access to any host features. This is essentially running as root right on the worker node and should be used sparingly

restricted

(n/a)

(OpenShift Default) Denies access to most host features and must run as random-selected uid.

In order for a pod to be able to run with additional access to the host system, it’s necessary to apply the SCC to the service account the pod executes as. One subtle difference between SCC and PSP is the RBAC around it; SCCs have a users'' property that lists the entities allowed to use the SCC while PSPs are controlled with roles and rolebindings. You can use the following command to apply the SCC to a service account, which under the covers adds the service accounts to the users'' property of the SCC.

oc adm policy add-scc-to-user <scc> system:serviceaccount:<namespace>:<serviceaccount>
oc adm policy remove-scc-from-user <scc> system:serviceaccount:<namespace>:<serviceaccount>

Identity Providers

OpenShift supports one or more Identity Providers as user directory sources for authentication. As OpenShift is a development platform, the default behavior is that any user that can authenticate to OpenShift is able to create a project (mappingMethod claim''). This behavior can be changed during installation or after installation by using mappingMethod lookup'', the downside is that the administrator must manually add user resources to OpenShift before they will be authorized to use the platform. https://docs.OpenShift.com/container-platform/3.11/install_config/configuring_authentication.html#LookupMappingMethod for more information.

Role-based Access Control

As Kubernetes RBAC was submitted upstream by Red Hat from OpenShift features, much of the RBAC in ICP is largely the same in ICP and OpenShift. Roles and ClusterRoles are groups of permissions on objects in the Kubernetes API. RoleBindings and ClusterRoleBindings are objects that bind roles to identities to access those permissions. Users, groups, and service accounts may have multiple role bindings which aggregated together gives them an access list of parts of the platform they may access.

One shortcut around assigning roles/cluster roles to users exists in the oc CLI, which under the covers creates a RoleBinding or ClusterRoleBinding, instead of the awkward kubectl create rolebinding'' and kubectl create clusterrolebinding'' commands:

oc adm policy add-role-to-user <role> <user>
oc adm policy add-cluster-role-to-user <role> <user>
oc adm policy remove-role-from-user <role> <user>
oc adm policy remove-cluster-role-from-user <role> <user>

ImagePolicy

OpenShift also contains an image policy, although it is not stored as a Custom Resource as it is in ICP. This can be configured on the master nodes. See:

Networking

From a developer point of view, the pod networking in OpenShift uses largely the same concepts as ICP and Kubernetes in general. There are some implementation differences in OpenShift networking to watch out for if you are managing the platform.

OpenShift SDN

The default networking implementation in OpenShift is the OpenShift SDN.

OpenShift SDN has with three different plugins that provide different levels of network isolation between projects:

  • ovs-subnet: (default) flat network that allows all projects to talk to all projects

  • ovs-multitenant: all projects are isolated from each other, with a single exception the default project where the OpenShift router and internal image registry run

  • ovs-networkpolicy: allows fine-grained control of network isolation using NetworkPolicy objects (equivalent to ICP).

When installing OpenShift, Red Hat recommends always installing using the ovs-networkpolicy plugin which provides near parity with ICP feature with Calico. To use this, add the following parameter to the ansible hosts file before installation:

os_sdn_network_plugin_name='redhat/OpenShift-ovs-multitenant'

Note that it’s possible to run Calico on OpenShift instead of Openshfit SDN; however Red Hat does not support this directly and the client will need to purchase support directly from Tigera. The list of additional vendor-supported network plugins are available here:

OpenShift SDN Architecture

OpenShift SDN networking components live in the openshift-sdn project in OpenShift, and consist of two daemonsets, ovs and sdn.

ovs is a containerized version of Open vSwitch which is an open source SDN software used most commonly in OpenStack. This will manage a bridge device, vxlan tunnel device for the pod network, and all of the virtual ethernet devices (veths) for each pod as they are created and destroyed.

sdn is a component used to program openvswitch by synchronizing routes to the other worker nodes and any cluster IP services created in the cluster. The routes are programmed as open vswitch flows and the cluster IPs are configured using netfilter (iptables) rules.

To dump the flows for debugging or informational purposes, you may install the `openvswitch'' package on any cluster node, and use `ovs-ofctl to view the flow table. See https://docs.OpenShift.com/enterprise/3.1/admin_guide/sdn_troubleshooting.html#debugging-local-networking for more information. This output is helpful to understand how pod traffic is forwarded.

In contrast to ICP/Calico, which uses a single controller pod running on the master nodes to orchestrate subnet selection, routes and network policy rules, and a daemonset `calico-node'' running across each cluster node to program iptables rules and do route propagation. In ICP/Calico, the `kube-proxy container running on every node programs the cluster IPs in iptables rules instead of the calico-node pod.

In both ICP and Calico cases, the daemonset runs as a privileged container on each host in order to have access to the host network.

IP Address Management

As in standard Kubernetes, both OpenShift and ICP have a pod overlay network where address space is defined for pods, and pod IP addresses are drawn from subnets selected from this address space. In ICP this was defined using the `network_cidr'' property in the installation config.yaml. OpenShift also has the same concept, where the cluster network CIDR defined in `osm_cluster_network_cidr in the ansible hosts file, the default is 10.128.0.0/14. You can view the subnet in the clusternetwork custom resource in OpenShift (oc get clusternetwork).

Every node in the cluster will receive a slice'' of this address space. One additional parameter in OpenShift is the osm_host_subnet_length, which defines the size of the subnets assigned to each node in the cluster where pods running on them will be assigned IP addresses from. In ICP, Calico automatically selected this size based on the number of nodes in the cluster and the size of the pod network, and was able to resize and steal'' subnets from other nodes when particular worker nodes exhausted their pool. In OpenShift this is a static length. The default value of this is 9, which indicates that every worker node will get 32-9=23 bits of subnet space (i.e. a /23 subnet, or 512 IP addresses). The assigned host subnets are stored in the hostsubnets Kubernetes custom resource (oc get hostsubnets). It’s important to select a subnet length that will satisfy both the number of worker nodes and the expected number of pods on each worker node in the cluster.

Like in ICP, there is an additional `service network'' overlay network, which is a non-overlapping address space with the pod network that ClusterIP services are defined on. In OpenShift the installation parameter for this is `openshift_portal_net.

Pod Routing and Route Propagation

In ICP, Calico propagated routes using a node-to-node mesh where every worker node became a ``router'' for its assigned subnet on the pod network and the routes were communicated using border gateway protocol (BGP). Since BGP is a standard protocol used on the internet, it was possible for non-cluster nodes to join the peer-to-peer mesh and the routes to be propagated outside of the cluster and potentially gain some visibility into the pod network with external tools. However, because of the node-to-node mesh there can be scalability issues when the cluster becomes very large, BGP route reflectors could be used to propagate routes instead.

In OpenShift, the routes are stored in Kubernetes resources and the ``sdn'' DaemonSet programs the routes on each cluster node as flows in the local openvswitch tables. There is a bridge interface on each node that all pods receive a port on, and a tunnel interface where all outbound pod network traffic is sent when the destination pod is not running on the local node.

The following documentation helps to understand the network flows:

Network Isolation

In contrast to ICP and Calico’s usage of iptables rules, OpenShift SDN uses VXLAN to perform project-level isolation. Every project is assigned a Virtual Network Identifier (VNID), and as traffic leaves the Open vSwitch tunnel, the VNID is added to the outgoing packet. When traffic reaches the destination, if the worker node does not have a policy (either the same VNID, or an explicit Open vSwitch flow from a Network Policy) that allows the traffic, it is dropped. As mentioned earlier the default'' namespace runs the router and registry and as such, every project is allowed to access this project, which is given the special VNID 0. It’s important for administrators not to expose default'' to users to deploy pods in general as all projects in the cluster will have network access to it.

You can read more details here:

In some environments, OpenShift may run on top of infrastructure that already uses VXLAN for isolation (such as VMware and NSX) and the VXLAN port used must be changed due to conflicts. This can be done by following the steps documented here:

NetworkPolicy

NetworkPolicy is largely the same in OpenShift as it is in ICP. There is one difference in that OpenShift only supports ingress NetworkPolicy, so network policies with egress rules do not work and egress network policy is controlled using a separate EgressNetworkPolicy object.

NetworkPolicy objects in OpenShift result in flow rules in Open vSwitch, and if using a podSelector to match pods, the more pods that match the rule, the more rules are created, which may cause some scalability issues. See documentation for an explanation:

EgressNetworkPolicy and EgressRouter

As mentioned in previous section, the OpenShift EgressNetworkPolicy is a separate object used to control egress traffic from pods to external subnets. These are implemented at Layer 3 in openflow table rules. The destinations may also be DNS names, but these are implemented using a DNS lookup of the name and the subsequent rules on the resolved IP address for the DNS record’s TTL. You can see more information in the documentation here: https://docs.OpenShift.com/container-platform/3.11/admin_guide/managing_networking.html#admin-guide-limit-pod-access-egress

OpenShift has an object that allows all egress to a particular external service go through a single node, called EgressRouter. This allows traffic coming from the cluster to an external service appear from a static IP and allows operations to whitelist that router. See: https://docs.OpenShift.com/container-platform/3.11/admin_guide/managing_networking.html#admin-guide-limit-pod-access-egress-router

DNS

ICP runs a DaemonSet across the masters containing CoreDNS for cluster DNS lookup and name resolution. DNS was only available inside of pods, as the kubelet would set each pod’s /etc/resolv.conf to point at the service IP address of the CoreDNS pod, and the host’s /etc/resolv.conf is used for upstream name resolution.

OpenShift 3.11 implements DNS slightly differently: SkyDNS runs on every node and is embedded within the atomic-OpenShift-node service listening on port 53. This node will sync service names and endpoints retrieved from etcd to the local SkyDNS. Every node in the cluster will have its /etc/resolv.conf rewritten to point at the local copy of SkyDNS. All pods will also have their /etc/resolv.conf rewritten to point at the IP address of the local host. This means that service names (using FQDN of the cluster internal domain) are resolvable even from cluster nodes.

OpenShift will not start if NetworkManager is not enabled on all nodes. Make sure that NetworkManager is managing all interfaces (NM_CONTROLLED=yes in /etc/sysconfig/network-scripts/ifcfg-eth*). A script that runs when NetworkManager brings up the interface will rewrite the local /etc/resolv.conf to point at SkyDNS; the upstream DNS servers are stored in /etc/origin/node/resolv.conf.

See the documentation for more information:

Note that OpenShift 4.x implements this differently and has moved to the more familiar CoreDNS.

Routes vs Ingress

In order to get external cluster traffic into the cluster, ICP used the Proxy Nodes which run an nginx-based ingress controller. Ingress resources stored in Kubernetes were used to program the nginx configuration to accept Layer-7 traffic based on specific rules, and could leverage certain nginx features like path-based rewrites and TLS termination using annotations on the ingress resource.

In OpenShift, there is a similar component running on the infra'' nodes called the Router. This is an HAProxy container, and runs in the special default'' project that all projects should have access to. OpenShift uses a special Route'' object that pre-dates Ingress'' resources in Kubernetes, which can be used to expose Layer 7 traffic, terminate TLS. There are a few more options that are exposed as first-class properties of Routes such as being able to passthrough TLS connections or re-encrypt them.

In later versions of OpenShift (3.10+), the router is able to translate Ingress'' objects to Routes''. However, HAProxy is not as feature-rich as nginx and as such some features in the ICP ingress controller are not available using OpenShift routes, most notably path-based rewrites. A workaround is to run a standalone nginx controller that can perform these rewrites as needed in each project, and expose that using through the OpenShift router.

When OpenShift is installed, it requires a wildcard domain pointing at the IP address or load balancer in front of the nodes where the router is installed (OpenShift_hosted_registry_routehost). All routes will by default be given a DNS name like <route-name>-<project-name>.<app-subdomain>.

More documentation about the default HAProxy router, including some advanced use cases like router sharding (which is similar to the ICP isolated proxy use case) is here: https://docs.OpenShift.com/container-platform/3.11/install_config/router/default_haproxy_router.html#install-config-router-default-haproxy

External Integration with F5 Load Balancer

Note that like ICP, there is an F5 BIGIP controller for OpenShift where a controller is able to program an F5 appliance through the API in response to Kubernetes resources. See: https://clouddocs.f5.com/containers/v2/OpenShift/

Operation – Cluster Management, Monitoring and Logging

Operation maybe one of the complex areas requires extra planning and effort to migrate from ICP to OpenShift.

Cluster Management

We mentioned the different options to access ICP and OpenShift in early chapters. From operation perspective either manual or automated, the command line tools (cli) might be the most relevant tool. The good news is that both platform support kubectl'' to operate your cluster. The not so good news is that both have their own flavor of cli (ICP has the cloudctl while OpenShift has oc). Most of the standard kubernetes tasks can be carried out by sticking to kubectl''. That puts migration as small effort to migrate any cloudctl'' command to either kubectl'' or ``oc'' or sunset them.

One area you need to pay attention is that OpenShift runs only on RHEL or RHCOS operating system. That may introduce some migration work when your ICP is running on non-RedHat OS. For example, if you have operation scripts handles the patches update on OS, service restart etc.

Monitoring

Both platforms are adopting the CNCF projects as de-facto standard when comes to monitoring. They are Grafana and Prometheus. ICP has fairly decent integration with both technologies and OpenShift 3.11 installs them by default. But this doesn’t mean the migration is that straightforward.

First, Prometheus may collect different set of metrics. It will be at least a medium level of effort to adjust the Prometheus Query Language and tested in new OpenShift platform.

Then, you might need to migrate the Grafana dashboards that purposely built for ICP. OpenShift comes with some sample dashboard like Docker or Kubernetes monitoring via Prometheus.

Alerting is another area you need to consider. In theory, OpenShift Prometheus supports AlertManager (can be installed as optional component). But ensuring the existing ICP alerts fully function in OpenShift including Notification by email, webhooks, Slack, PagerDuty and alert Silencing, aggregation, inhibiting can take quite bit of effort.

Logging

ICP deploys an ELK (ElasticSearch, Logstash, Kibana) stack, referred to as the management logging service, to collect and store all Docker-captured logs.

OpenShift uses the EFK (ElasticSearch, fluentd, Kibana) stack as a logging solution. The main difference comparing to ICP is how the logs are shipped out of the cluster with Fluentd. But most of that is implementation detail and relatively transparent to the application and end user.

Migration Strategy – ICP Cluster migration

Migration Strategy == Migration Strategy – ICP Cluster migration :toc: :toc-placement!:

OpenShift Installation

To migrate from ICP to Openshift, the recommended approach is a blue-green deployment where an Openshift cluster is created alongside an existing ICP cluster, the workload is migrated from ICP to Openshift, load balancers or DNS entries are updated to point clients at the new cluster, and the ICP cluster is retired.

We highly recommend infrastructure automation to create new clusters for Openshift. This provides the quickest path for new cluster creation. We have published infrastructure automation templates for ICP on various cloud platforms, for example:

In scenarios where resources are limited, the approach involves draining and removing under-utilized ICP worker nodes and using the capacity to build a small Openshift control plane with a few workers. Depending on available capacity, an Openshift cluster can start as a single master and scale up to three masters as capacity is made available.

User Migration

User Authentication migration – LDAP

Kubernetes does not have users; it’s up to the Kubernetes distribution to provide an authentication endpoint that performs user authentication and identity mapping to either a User or a Group. Kubernetes does have roles and cluster roles, which are used to group permissions, and rolebindings and clusterrolebindings, which are used to assign permissions to particular users or groups.

For more information about how authentication is implemented in Kubernetes, see:

In ICP, the internal auth-idp component is used as an OIDC provider that authenticates users. This component can be configured from the UI and will connect to LDAP on behalf of the cluster to authenticate users. The Teams concept is used to group together users or groups from LDAP into logical groups and is managed by the auth-idp component and persisted in mongodb.

Openshift 3.11 has a similar component embedded in the API server that performs authentication on behalf of the cluster. However, it does not have a UI to configure LDAP, so it takes some work during installation or after installation to configure LDAP.

Openshift 4.x uses a separate operator to perform authentication, and another operator manages that operator and its configuration using a CustomResourceDefinition to configure LDAP and other identity providers.

In our migration scenario, we specifically we looked at migrating an existing ICP LDAP connection to Openshift 3.11.

Our LDAP server had the following contents:

dn: dc=internal-network,dc=local
dc: internal-network
objectClass: top
objectClass: domain

dn: cn=ldapadm,dc=internal-network,dc=local
objectClass: organizationalRole
cn: ldapadm

dn: ou=People,dc=internal-network,dc=local
objectClass: organizationalUnit
ou: People

dn: ou=Group,dc=internal-network,dc=local
objectClass: organizationalUnit
ou: Group

dn: cn=binduser,dc=internal-network,dc=local
cn: binduser
objectClass: organizationalRole
objectClass: top
objectClass: simpleSecurityObject

dn: cn=user1,ou=People,dc=internal-network,dc=local
cn: user1
objectClass: person
objectClass: simpleSecurityObject
objectClass: uidObject
objectClass: top
sn: one
uid: user1

dn: cn=dev1,ou=Group,dc=internal-network,dc=local
cn: dev1
objectClass: groupOfUniqueNames
objectClass: top
uniqueMember: cn=user1,ou=People,dc=internal-network,dc=local
uniqueMember: cn=user2,ou=People,dc=internal-network,dc=local

dn: cn=user2,ou=People,dc=internal-network,dc=local
cn: user2
objectClass: person
objectClass: simpleSecurityObject
objectClass: uidObject
objectClass: top
sn: user two
uid: user2

dn: cn=clusteradmin,ou=People,dc=internal-network,dc=local
cn: clusteradmin
objectClass: person
objectClass: simpleSecurityObject
objectClass: uidObject
objectClass: top
sn: cluster admin
uid: clusteradmin

dn: cn=admin,ou=Group,dc=internal-network,dc=local
cn: admin
objectClass: groupOfUniqueNames
objectClass: top
uniqueMember: cn=clusteradmin,ou=People,dc=internal-network,dc=local

dn: cn=dev2,ou=Group,dc=internal-network,dc=local
cn: dev2
objectClass: groupOfUniqueNames
objectClass: top
uniqueMember: cn=user3,ou=People,dc=internal-network,dc=local
uniqueMember: cn=user4,ou=People,dc=internal-network,dc=local

dn: cn=user3,ou=People,dc=internal-network,dc=local
cn: user3
objectClass: person
objectClass: simpleSecurityObject
objectClass: uidObject
objectClass: top
sn: three
uid: user3

dn: cn=user4,ou=People,dc=internal-network,dc=local
cn: user4
objectClass: person
objectClass: simpleSecurityObject
objectClass: uidObject
objectClass: top
sn: four
uid: user4

The ICP configuration appeared as follows:

LDAP settings in ICP

The matching Openshift configuration was configured under identityProviders on each master host in /etc/origin/master/master-config.yaml. Once this was configured we restarted docker on each master host to restart the API server. The configuration is as follows:

identityProviders:
  - name: "rhos-ldap"
    challenge: true
    login: true
    mappingMethod: claim
    provider:
      apiVersion: v1
      kind: LDAPPasswordIdentityProvider
      attributes:
        id:
        - dn
        email:
        - mail
        name:
        - cn
        preferredUsername:
        - uid
      bindDN: "cn=binduser,dc=internal-network,dc=local"
      bindPassword: "xxx"
      insecure: true
      url: "ldap://192.168.100.4:389/dc=internal-network,dc=local?uid?sub?(objectclass=person)"

Pay particular interest to the url. The format of the URL is

ldap://host:port/basedn?attribute?scope?filter

We have translated this from the ICP configuration, where attribute and filter are built from the User filter in the ICP configuration. The query it uses is:

(&(attribute=%v)(filter))

Openshift has explicit user and group resources which the API server manages. You can list them using the familiar oc get users and oc get groups commands as well as create additional ones.

As Openshift is a developer platform, the default mappingMethod claim allows anybody that successfully authenticates access to the platform to login and create projects. When authentication is successful, the platform will create a user resource automatically. The ICP model denies access to any users in LDAP that are not part of a team. To match the ICP model and deny access to anybody not explicitly added to Openshift there are two options:

  • use the mappingMethod lookup. However this requires additional overhead as the administrator must individually create users in the Openshift platform before they are given access to log in to Openshift.

    In our case, we created a user for user1, created an identity for it in ldap, and then mapped them together:

    $ oc create user user1
    user.user.openshift.io/user1 created
    
    $ oc create identity rhos-ldap:cn=user1,ou=People,dc=internal-network,dc=local
    identity.user.openshift.io/rhos-ldap:cn=user1,ou=People,dc=internal-network,dc=local created
    
    $ oc create useridentitymapping rhos-ldap:cn=user1,ou=People,dc=internal-network,dc=local user1
    useridentitymapping.user.openshift.io/rhos-ldap:cn=user1,ou=People,dc=internal-network,dc=local created
  • Leave the default mappingMethod claim but deny access to create new projects in Openshift. By default the system:authenticated group (i.e. anybody in LDAP) is given the self-provisioner cluster-role, which allows project creation. Removing the role removes the overhead of having to create new users as they log in, but also prevents authenticated users from consuming resources in the platform without cluster administrator action. See: https://docs.openshift.com/container-platform/3.11/admin_guide/managing_projects.html#disabling-self-provisioning

    $ oc patch clusterrolebinding.rbac self-provisioners -p '{ "metadata": { "annotations": { "rbac.authorization.kubernetes.io/autoupdate": "false" } } }'
    $ oc patch clusterrolebinding.rbac self-provisioners -p '{"subjects": null}'
  • We think this matches ICP the closest, but allowing users to create projects on their own has some advantages in developer scenarios. Using the above policy makes sense in production clusters but can be relaxed in development/test clusters.

Group migration – LDAP

Note that the Openshift api server does not query groups from LDAP; group definitions must be synced manually. The documentation around this is here: https://docs.openshift.com/container-platform/3.11/install_config/syncing_groups_with_ldap.html

In our scenario we had users in the tree under ou=People, and groups under ou=Group. Three groups were created (dev1, dev2, and admins). We used the following rfc2307 LDAP sync config:

kind: LDAPSyncConfig
apiVersion: v1
url: ldap://192.168.100.4:389/dc=internal-network,dc=local
bindDN: "cn=binduser,dc=internal-network,dc=local"
bindPassword: "xxxx"
insecure: true
rfc2307:
    groupsQuery:
        baseDN: "ou=Group,dc=internal-network,dc=local"
        scope: sub
        derefAliases: never
        pageSize: 0
        filter: "(objectclass=groupOfUniqueNames)"
    groupUIDAttribute: dn
    groupNameAttributes: [ cn ]
    groupMembershipAttributes: [ uniqueMember ]
    usersQuery:
        baseDN: "ou=People,dc=internal-network,dc=local"
        scope: sub
        derefAliases: never
        pageSize: 0
    userUIDAttribute: dn
    userNameAttributes: [ uid ]
    tolerateMemberNotFoundErrors: false
    tolerateMemberOutOfScopeErrors: false

Observe how this maps to the configuration in ICP; the groups are of object class groupOfUniqueNames and the uniqueMember attribute contains the members of the group which will be in turn queried.

Running this command will add some Openshift Group resources that can be assigned roles.

$ oc adm groups sync --sync-config=rfc2307_config.yaml  --confirm
group/dev1
group/admin
group/dev2

The result is three groups, with the user mappings as shown.

$ oc get groups
NAME      USERS
admin     clusteradmin
dev1      user1, user2
dev2      user3, user4

As this is a manual process that produces static user/group mappings, it may be required to run this on a schedule that updates and prunes groups in an ongoing basis.

One additional implementation note is that Openshift issues Opaque tokens; since the authentication module is embedded in the API server it is able to validate the tokens internally. In ICP, the authentication token issued by the auth service is a signed JWT that contains an embedded list of groups that Kubernetes uses to validate permissions. In the next session when we discuss RBAC, we can see how rolebindings and clusterrolebindings are bound to these groups.

User Authorization Migration (RBAC)

Kubernetes ships with some out of box user-facing cluster roles: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles

  • cluster-admin

  • admin

  • edit

  • view

ICP ships with five additional user-facing cluster roles which correspond to the roles used in teams:

  • icp:admin

  • icp:edit

  • icp:operate

  • icp:teamadmin

  • icp:view

These are implemented as aggregated roles, which means there are several roles where their permissions are union-ed together to provide a full set of permissions for the user. The permissions are contained in aggregate roles. Note that the admin role contains all of the permissions that the edit role contains, etc.

  • icp-admin-aggregate

  • icp-edit-aggregate

  • icp-operate-aggregate

  • icp-view-aggregate

Openshift ships with four user-facing cluster roles:

  • admin

  • basic-user

  • edit

  • view

Since RBAC is common in both Openshift and ICP, it’s tempting to just export these 9 roles to Openshift. However, some Openshift-specific resources are added to the Kubernetes out-of-box roles. These are in the *.openshift.io apiGroups defined in the roles for the above.

As clusterroles are whitelists of permissions, and access in Kubernetes is a union of all of the roles bound to the identity, one potential way of quickly migrating permissions is to import the cluster roles from ICP, assign the ICP roles to the users and groups, then assign the Openshift view role to all users so that the projects will appear in the CLI and UI. The Openshift view role will overlap with the icp:view role but the Openshift view role will enable view of the Openshift specific resources (e.g. projects, builds, etc). This will allow the same access to the Kubernetes API that was assigned in ICP. Additional access to Openshift specific objects (e.g. to create a build, deployment configs, etc) may be added by assigning Openshift specific roles like edit, admin, etc.

Here is one team that we created with the following group/user mappings:

$ cloudctl iam team-get dev-1
Name: dev1
ID: dev-1

ID      Type    Name    Email   Roles
dev1    group   -       -       Viewer
user1   user    user1   -       Administrator

With the following resources assigned:

$ cloudctl iam resources -t dev-1
CRN
crn:v1:icp:private:k8:jkwong-icp-31-cluster:n/dev1:::

We exported the 9 ICP roles from ICP as yamls, and imported them into openshift.

$ kubectl get clusterroles | grep icp | awk '{print $1}' | xargs -n1 -I{} kubectl get clusterrole {} -o yaml  | tee  all-roles.yaml

...

$ oc apply -f all-roles.yaml

Then we created the associated resources (i.e. the namespace/project) and mapped the same permissions using the following commands:

$ oc new-project dev1
Now using project "dev1" on server "https://console.jkwong-ocp.internal-network.local:443".

You can add applications to this project with the 'new-app' command. For example, try:

    oc new-app centos/ruby-25-centos7~https://github.com/sclorg/ruby-ex.git

to build a new example application in Ruby.

We initially gave access to both of these entities:

$ oc adm policy add-role-to-group view dev1
role "view" added: "dev1"
$ oc adm policy add-role-to-user view user1
role "view" added: "user1"

Next we assigned the ICP role to give the same access that the user had in ICP.

$ oc adm policy add-role-to-group icp:view dev1
role "icp:view" added: "dev1"
$ oc adm policy add-role-to-user icp:admin user1
role "icp:admin" added: "user1"

Workload Migration

In this section we used a case study of migrating our cloud native reference application BlueCompute that was running on ICP to Openshift.

For detailed steps on how to migrate Bluecompute from ICP to Openshift, refer this doc - BlueCompute on OpenShift.

Modify containers to run as non-root and other mitigations

Following some guidelines here:

Openshift specific:

In general, when authoring containers, developers should try run with the least privileges as possible.

Modifying a container’s USER

If a container’s Dockerfile does not set a USER, then it runs as root by default.This is dangerous because root inside a container is also root on the host. Openshift prevents containers from running as root by applying a default restricted SecurityContextConstraint. When a container is started, Openshift will randomly select a uid from a range that does not have access to anything on the worker node in case a malicious container process is able to break out of its sandbox.

In most application scenarios, the actual user a process runs as doesn’t matter, but there are some legitimate cases where the container expects to be run as a particular user, such as some database containers or other applications where it needs to read or write to its local filesystem or to a persistent volume. A simple mitigation is to add the USER directive to the Dockerfile before the CMD or ENTRYPOINT so that the main container process does not run as root, e.g.

USER 1000

Then making sure the files it modifies contains the correct permissions.

It’s better to provide a numeric value rather than an existing user in /etc/passwd in the container’s filesystem, as Openshift will be able to validate the numeric value against any SCCs that restrict the uids that a container may run as. In the case where we use a third party container and we are not able to modify the Dockerfile, or the USER directive refers to a user that corresponds to something in /etc/passwd, we can add the securityContext section to the podspec to identify the UID that it the pod refers to. For example, in BlueCompute the MySQL container we used is from dockerhub, but they allow running as USER mysql which corresponds to uid 5984 in /etc/passwd, so we added this section to the podSpec in the deployment:

securityContext:
  runAsUser: 5984
  runAsGroup: 5984
  fsGroup: 1000

The fsGroup is useful to provide supplemental groups which are added to the container’s processes. For example, in the above case the container process can also interact with files owned by group 1000, which might be helpful if using existing shared storage where there are directories owned by the group.

Modifying a container’s filesystem for read/write

If filesystem access is needed in the container filesystem, then those files should be owned by and read/writable by the root group. In Openshift, the arbitrary uid used by the restricted SCC will be added to the root group. Directories that must be read to/written from as scratch space may add the following to the Dockerfile:

RUN chgrp -R 0 /some/directory && \
    chmod -R g=u /some/directory

Another strategy that we’ve had success with is to create an emptyDir volume and mount it to the directory, which Kubernetes will create and destroy with the pod. The emptyDir volume is owned by root but is world writable and can be used as local storage for the container. This also helps someone reviewing the pod definition identify which directories will be written to.

volumes:
- emptyDir: {}
  name: database-storage

Using a nonroot SecurityContextConstraint

In cases where existing shared storage is attached to the container as a volume, and the container must write to the filesystem as a particular uid, we can run the pod under a service account, and assign the "nonroot" scc to the service account. This relaxes the restrictions on the uid and allows the container process to be executed as some specific UID, but not root. This is essentially equivalent to ICP’s ibm-restricted-psp security policy. For example, in our above example our MySQL container runs as uid 5984, but Openshift blocks the execution since the uid is not within the allowed range forrestricted SCC, so we created a service account inventory-mysql for the pod, allow it to use the nonroot SCC and set the deployment to run as this service account.

$ oc create serviceaccount inventory-mysql
$ oc adm policy add-scc-to-user nonroot -z inventory-mysql
$ oc patch deploy/inventory-mysql --patch \
     '{"spec":{"template":{"spec":{"serviceAccountName":"inventory-myql"}}}}}'

Using anyuid SeucirtyContextConstraint

In cases where running as the root user is absolutely necessary, we can leverage a serviceaccount and assign the anyuid SCC to allow the container to run as root. For example, in our BlueCompute reference application, couchdb only works when it runs as root due to the way it tries to change ownership of the data directories. We will allow couchdb to run as root user:

Steps in ICP:

$ kubectl ceate serviceaccount -n bluecompute customer-couchdb
$ kubectl create role psp-ibm-anyuid-psp -n bluecompute --verb=use \
     --resource=podsecuritypolicy --resource-name=ibm-anyuid-psp
$ kubectl create rolebinding couchdb-ibm-anyuid-psp -n bluecompute \
     --role=psp-ibm-anyuid-psp --serviceaccount=bluecompute:customer-couchdb

Now we add the serviceaccount to the podspec in the statefulset.

$ kubectl patch deploy/couchdb --patch \
     '{"spec":{"template":{"spec":{"serviceAccountName":"customer-couchdb"}}}}}'

Similarly, in Openshift:

$ oc create serviceaccount -n bluecompute customer-couchdb
$ oc adm policy add-scc-to-user anyuid -z customer-couchdb
$ oc patch deploy/couchdb --patch \
     '{"spec":{"template":{"spec":{"serviceAccountName":"customer-couchdb"}}}}}'

DevOps and Developer toolchains

Generally, ICP was not opinionated on DevOps, and if the toolchain is outside of the platform there is no reason for this to change when migrating to Openshift.

Openshift’s value proposition does include some developer productivity tools such as Source-to-Image (S2I) and containerized Jenkins, as well as tech preview and support for Openshift Pipelines and CodeReady Workspaces, but in a migration scenario where we are migrating existing workload from ICP these are not required to change. We can simply treat Openshift as another Kubernetes platform we are deploying to.

Jenkins server migration

One scenario that bears mentioning is if the CI toolchain was deployed to ICP, typically using the community Helm chart. For example, In the BlueCompute case, this was done using a containerized Jenkins instance. All of the stages in the pipelines run as containers in Kubernetes using the Kubernetes plugin.

The best practice around Jenkins is for the pipelines themselves to be written using a declarative Jenkinsfile stored with the code. The pipeline logic is stored with the application source code and itself treated as part of the application. In BlueCompute, Jenkinsfiles are stored with each microservice, so we only needed to create an instance of Jenkins in Openshift and import the pipelines to get our builds to work.

Openshift has both ephemeral and persistent Jenkins in the catalog which is very comparable to the Jenkins Helm chart. This instance of Jenkins will automatically install the Kubernetes and Openshift client plugins, so Jenkinsfile that uses podTemplates that spin up containers to run stages in the pipeline should "just work".

Running Jenkins pipeline stages as containers in Openshift

As pipeline stages are run in containers, there are security issues when a particular stage attempts to run a container that requires root access, mounts a hostpath, etc, just like application containers. Most runtime build images don’t need to run as root (e.g. maven, gradle, etc), which means they should run just fine in Openshift.

One security problem when using Kubernetes plugin is how to build the container image itself, which must run in a container. In Openshift this is further complicated that the default restricted SCC disallows host mounting the docker socket, running as root, or running in privileged mode without changing the SCC. The community is still attempting to resolve this problem, since traditionally the Docker tool requires root and many of the functions involved in building a container image requires various elevated privileges.

There are a few projects at various stages in development as of this writing that can build container images without docker, which run fine outside of a container, but none are perfect inside of a container.

When using these tools, we can relax the security constraints on them by adding the SCC to the jenkins service account in the namespace where the pod runs. For example, kaniko only requires root access, so we can simply add the anyuid scc to enable kaniko:

$ oc adm policy add-scc-to-user anyuid -z jenkins

Note the security implications of the above. While containers are running with elevated privileges, any other workload running on the same worker node may be vulnerable, as Jenkins build stages may run as root. Additionally since the developer controls the commands used in the pipeline stage, this essentially gives developers root access on the worker node the build runs on. In shared clusters, it may make sense to use a nodeSelector on all projects where jenkins will run to isolate jenkins workloads to just a few nodes: https://docs.openshift.com/container-platform/3.11/admin_guide/managing_projects.html#setting-the-project-wide-node-selector

Migration of Container builds to Openshift BuildConfigs

If we are running the Jenkins pipeline stages in Jenkins on Openshift, we can leverage the Openshift Jenkins client plugin and the Openshift BuildConfig to build the container image, which provides a controlled environment for producing the container image without exposing root access on any worker nodes. By providing just a Dockerfile and a build context, Openshift will build and push the resulting image to the Openshift private registry and track it using an ImageStream, and we can then provide an additional stage using skopeo to push this to an external registry. BuildConfig limits the amount of damage the developer can do because they are not explicitly running commands as root.

We have posted an example pipeline we used at a customer that leverages this at the following git repository:

Continuous Deployment and GitOps adoption

GitOps Migration

In some of our previous CICD examples, we tightly coupled Continuous Integration (CI) with Continuous Deployment (CD) in a single pipeline. This makes it difficult for deployments to become portable as the build is highly dependent on the deployment target.

The community has been moving toward declarative deployment and some terms like GitOps have been gaining popularity. In this model, CI performs pre-release verification with build and test of the application. The output of CI is the "document" that describes what to deploy, which in Kubernetes includes the container image, and the associated resource yamls that can also be packaged into Helm charts or kustomize applications. We leave the "how" and "where" to deploy to CD. In some organizations these are separate (sometimes siloed) organizations, so the "document" approach allows a better separation of concerns.

In true GitOps, all operations have a matching action in git (pull requests, merges, etc). Deployment documents are committed to a git repo, which can trigger webhooks to begin the CD process. Code promotion involves merges to git repos representing the desired state of upper environments. The git commit log could be used for approval and deployment history but probably shouldn’t be used for audit (as git history can be manipulated).

We have posted a sample CI pipeline where we decoupled the CI from the CD. (link to be provided)

As there is no upgrade path from Openshift 3.x to Openshift 4.x, separating CI from CD and adopting GitOps will allow you to add a new Openshift 4.x environment as an additional deployment target with minimal effort. Having a single repository representing a deployment to a target environment also allows us to scale out to additional target environments as application adoption requires it.

Decoupling CD from CI

As CI is common to both ICP and Openshift platforms, when we move to a declarative deployment model, the CD is the only part that changes. A first step toward GitOps and declarative deployments is to decouple CD from CI.

In our Bluecompute case study, we had tightly coupled CI/CD pipelines and broke it into two, CI, and CD (to dev). The end of our CI process generates an image, and embeds the URL into Kubernetes yamls and pushes it to a second Git repository representing a deployment. The CD pipeline is triggered from changes to the deployment git repository, which executes a deployment script (in jenkins) into the target environments. Generally we can expect one target environment type (e.g. dev, qa, stage, prod)per git repository, and one pipeline per environment (e.g. prod-east, prod-west). It’s important to be as declarative as possible.

There are a few projects, including Razee (included with Cloud Pak for Applications), that can monitor and perform deployments from git repositories. Ideally, the deployment is performed from inside the target environment to reduce the number of credentials being stored in the deployment system. When these projects mature, we can migrate to them. For now, we used our existing Jenkins server to monitor and perform the deployment.

Using a service account from outside of the platform to perform deployment

Openshift ships with a service account in each project deployer, and a cluster role system:deployer which contains some permissions it uses internally to perform DeploymentConfig rollouts. As we are not using DeploymentConfig, this was not enough for our CD process, since all of the permissions are against the built-in Openshift resources.

In some of our client engagements, the CD process performs a deployment using a service account that has limited privileges. For our Bluecompute case study, we created a jenkins service account that performs the deployment in the target namespace.

oc create serviceaccount jenkins -n bluecompute

We created the following deployer clusterrole that allows a CD tool to do its job on generic Kubernetes resources:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    openshift.io/description: Grants the right to deploy within a project.  Used primarily
      with service accounts for automated deployments.
    openshift.io/reconcile-protect: "false"
  name: deployer
rules:
- apiGroups:
  - extensions
  attributeRestrictions: null
  resources:
  - daemonsets
  - deployments
  - deployments/rollback
  - deployments/scale
  - ingresses
  - networkpolicies
  - replicasets
  - replicasets/scale
  - replicationcontrollers/scale
  verbs:
  - create
  - delete
  - deletecollection
  - get
  - list
  - patch
  - update
  - watch

Then we apply this new role and also the view role to the jenkins service account so it can see the project.

oc adm policy add-role-to-user view system:serviceaccounts:bluecompute:jenkins
oc adm policy add-role-to-user deployer system:serviceaccounts:bluecompute:jenkins

We can also generate the kubeconfig.yaml needed for our CD tool to connect to the cluster. First, find the serviceaccount token:

oc describe serviceaccount jenkins
Name:                jenkins
Namespace:           bluecompute
Labels:              <none>
Annotations:         <none>
Image pull secrets:  jenkins-dockercfg-gmhlq
Mountable secrets:   jenkins-token-r4rcs
                     jenkins-dockercfg-gmhlq
Tokens:              jenkins-token-b7lvw
                     jenkins-token-r4rcs
Events:              <none>

Get the token from the secret, which looks like a JWT.

oc describe secret jenkins-token-b7lvw
Name:         jenkins-token-b7lvw
Namespace:    bluecompute
Labels:       <none>
Annotations:  kubernetes.io/created-by=openshift.io/create-dockercfg-secrets
              kubernetes.io/service-account.name=jenkins
              kubernetes.io/service-account.uid=64aed8e2-c900-11e9-b106-005056a86156

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:          1070 bytes
namespace:       11 bytes
service-ca.crt:  2186 bytes
token:           eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJ...

Create a new kubeconfig yaml and log in to Openshift using the token:

export KUBECONFIG=/tmp/jenkins-kubeconfig.yaml
oc login <openshift URL> --token=eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJ...

The file /tmp/jenkins-kubeconfig.yaml now contains the configuration including embedded credentials for connecting to Openshift as the service account. Use caution when distributing this file as the token does not expire. In our Bluecompute case study, we stored this as a "Secret file" credential in Jenkins. You can invoke the API, for example to patch a deployment, using:

kubectl --kubeconfig=<mykubeconfig> apply -f deployment.yaml
Performing deployments from inside the platform

As the number of environments increases, it can become difficult to manage credentials for each environments, particular if they are dynamically created using Terraform, Cluster API, or some other infrastructure automation. One interesting model is to run a controller or operator from inside each cluster that is created and have it monitor the git repository to perform deployments. Razee is one such project, but there are others, including ArgoCD. Another approach is to just run a simple Tekton pipeline that monitors git repository changes and executes the deployment as the service account from within the cluster. This way the credentials do not need to be extracted from each cluster as it is created and managed individually.

We will publish an example of this (link to be provided).

Migration of Kubernetes Artifacts

DeploymentConfig vs Deployment

Openshift has its own deployment resources called DeploymentConfig and ReplicationController. These pre-date the Deployment concept in Kubernetes and provide some additional functionality such as the ability to trigger deployments from upstream image changes. As Openshift supports these same Kubernetes-native objects, the least effort in a migration is to reuse the resources used to deploy to ICP.

Routes vs Ingress

Openshift also has the concept of Route instead of Ingress, which predates the first-class Ingress object. Openshift will automatically convert Ingress resources to a Route and exposes them through the router. See here for more information: https://blog.openshift.com/kubernetes-ingress-vs-openshift-route/

As the implementation of how ingress resources are exposed are dependent on the ingress controller type, there are some features that are not available in Openshift routes. One popular example is path-rewrites supported by annotations, which were performed by the nginx ingress controller in ICP. A mitigation to this is to add the nginx ingress controller between the router and services in Openshift that monitors a specific ingress class, and expose the nginx ingress controller through a route.

Helm Charts

Some development organizations may have adopted use of Helm Charts as a packaging mechanism for Kubernetes resources. As has been discussed frequently, Openshift does not natively support Helm chart based deployment. This is because it is difficult to configure the server-side component (Tiller) in a secure manner. Helm does have some advantages, such as a robust templating language, versioning, and package dependency management. A lot of community software is already packaged as Helm charts, although many of them do not work out of the box in Openshift due to the problems with SecurityContextConstraints (discussed earlier).

If Helm charts are already used for package distribution in your application, we can continue to package applications using them as part of the deployment artifacts. The deployment artifact in this case becomes the Helm chart in addition to generating the deployment values (i.e. values.yaml). The deployment procedure involves rendering the template locally into raw Kubernetes yamls (helm template), then deploying them using the kubectl or oc CLI into the target cluster. We have documented the approach here: https://github.com/ibm-cloud-architecture/refarch-cloudnative-kubernetes/blob/spring/docs/openshift/README.md

Helm v3, which is currently in beta, is also a possibility for client-side rendering, as tiller is embedded into the helm client and there is no server-side component. See the recent announcement about the beta as well as the related documentation: https://helm.sh/blog/helm-v3-beta/

In the near term, Cloud Paks are distributed as Helm charts and the ICP common services layer will include server side tiller. However it is not recommended to depend on this for application deployment as Cloud Pak will be moving away from server-side Tiller and into an Operator model.

Using a Backup/Restore approach using Velero

Converting PodSecurityPolicy to SecurityContextConstraints

NetworkPolicy migration

Application LoadBalancer cutover

Storage Migration

Storage Migration

We will focus on Kubernetes Storage under the context of ICP to OCP migration. For detail storage and Kubernetes usage, please reference the Kubernetes Storage Cookbook.

The migration has to take into consideration of both the Kubernetes Storage Provider and Storage consumer (database or application).

Storage Provider

In general, Kubernetes supports quite a few storage providers including hostPath, NFS, Ceph, Gluster, vSphere, minio, Cloud-based storage (S3 etc.). And these providers can be deployed either as a part of a Kubernetes cluster (internal storage) or storage provided by an external service (external storage). For the migration, we’ll focus on the internal storage or in-cluster storage provider.

Following storage can be hosted on ICP cluster nodes:

  • GlusterFS

  • Ceph block storage by using Rook

  • Minio

Red Hat Openshift support both GluserFS and Ceph as in-cluster storage providers. Haven’t heard the official support for Minio.

There is no migration path or tools available to migrate ICP storage nodes to Openshift. So, it boils down to handle the migration from the storage consumer’s aspect.

If you are using external storage provider, as far as it is supported by Openshift (all do except Minio), you just need to migrate the storage consumer and leave the external storage provider as-is.

If you are using internal storage provider, you need to setup the Openshift Storage nodes, either GlusterFS or Ceph, using the same/similar spec as in ICP in terms of disk size, storage type, number of nodes. Then, proceed to storage consumer migration.

Storage Consumer

Each client might have different storage consumption pattern, we’ll try to categorize them into the following:

  • Container applications requires persistent Storage

  • Kubernetes Statefulset application

  • Databases running on Kubernetes such as MongoDB, MySQL, Cloudant etc.

We’ll assume that all these storage needs are implemented as Kubernetes recommended Persistent Volume (PV) and Persistent Volume Claims (PVC).

When it comes to migration to OCP, it really becomes a storage backup and restore discussion. Depends on the storage consumer type (database vs. custom application), it can be done with:

  • Kubernetes PV backup and restore

  • Using Application/Database native backup-restore tools

This guide will be focus on the first approach where you migrate kubernetes PV.

One common approach of backing up Kubernetes PV is the Velero project from Heptio. The concept is Velero will take your PV snapshots, stores it on object storage (like S3 or Minio). Then, you can restore it to another Kubernetes cluster.

For detail on how the tool works in generic Kubernetes, please reference this blog post

Still, there are some limitations with Velero approach. For example:

  • It does not support the migration of persistent volumes across cloud providers.

  • Velero + Restic currently supports backing up to only S3 compatible object storage.

Platform Data Migration

Working in progress

Monitoring Data

Historical Log Data