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 |
For further reading, you can check this blog published earlier https://apps.na.collabserv.com/blogs/ca5e7833-78b8-481c-8a14-ba70b22a20ce/entry/Comparing_IBM_Cloud_Private_ICP_with_RedHat_OpenShift?lang=en_us
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.
-
Install HELM either through open source or through IBM Cloud Paks. An example of this is here (https://github.com/ibm-cloud-architecture/refarch-cloudnative-kubernetes/tree/spring#deploy-bluecompute-to-an-OpenShift-cluster)
-
Take existing Docker Images and applications, update YAML, and create a project with the oc tool. You can then use one of the mechanisms described earlier. This will require you to update existing CI/CD pipleines but moves you closer to the OpenShift environment.
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:
-
[Kabanero]: https://kabanero.io, which consists of CodeWind https://codewind.dev for IDE extensions to developer tools like Eclipse and VSCode, and Appsody https://appsody.dev for building templates for popular runtimes
-
Razee https://razee.io for Continuous Deployment
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 directdocker 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.
For more information, see here: https://docs.OpenShift.com/container-platform/3.11/dev_guide/builds/index.html
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.
For more information, see here: https://docs.OpenShift.com/container-platform/3.11/dev_guide/deployments/how_deployments_work.html
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.
See here for more information: https://docs.OpenShift.com/container-platform/3.11/dev_guide/templates.html
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:
ICP (3.2) hardware requirement guide - https://www.ibm.com/support/knowledgecenter/SSBS6K_3.2.0/supported_system_config/hardware_reqs.html
OpenShift (3.11) hardware requirement - https://docs.OpenShift.com/container-platform/3.11/install/prerequisites.html#hardware
OpenShift 4.2 system requirement - https://docs.openshift.com/container-platform/4.2/welcome/oce_about.html
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.
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>
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
defaultproject 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 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 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 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:
Terraform Examples for OpenShift 4.x: https://github.com/ibm-cloud-architecture/terraform-openshift4-aws https://github.com/ibm-cloud-architecture/terraform-openshift4-azure https://github.com/ibm-cloud-architecture/terraform-openshift4-gcp
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:
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
claimbut deny access to create new projects in Openshift. By default thesystem:authenticatedgroup (i.e. anybody in LDAP) is given theself-provisionercluster-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.
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.