Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spec: Kubernetes and COE agnostic support #329

Merged
merged 5 commits into from
Aug 10, 2017

Conversation

rthallisey
Copy link
Contributor

Spec outlining the work for supporting Kubernetes.

#233

@rthallisey rthallisey force-pushed the multiple-coe-spec branch 7 times, most recently from 466eb30 to 36d229d Compare August 2, 2017 19:46
Copy link
Contributor

@jmrodri jmrodri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that would be nice is if we could identify how we could do this over time. Can we spread it over a few PRs or sprints? Some minor change requests.


## Introduction
The ansible-service-broker and all of the tooling around it
(apb-examples, catasb, adn ansible-playbook-bundles) specifically target
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TYPO: adn -> and

should be cluster agnostic.

## Problem Description
Issues having been coming up during testing because the broker does not target
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issues have been coming up...


### Hierarchy Diagram
This diagram represents if the current state of code were split into two
files.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This table represents if the code were split into two files.

The main issue with packaging is that folks using Kubernetes may only want the
Kubernetes client. The package ```origin-clients``` installs both the OpenShift
and Kubernetes clients. If this the container size isn't an issue, then there
may not be a problem using ```origin-clients```.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be two parts to this. 1) the code being compiled into the broker for both 2) the branches will require specific clients i.e. openshift.go would require oc client. We could put in code to only require the specific oc client IF openshift.go is being invoked.

There was a blog post Shawn shared with me about plugins in go programs. I haven't read it yet but if there was a way to make these pieces more "pluggable" that could help.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin package is what @jmrodri is referring to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently plugins only work on Linux.


### apb-examples
- Add support for Kubernetes to ```oc-login.sh``` script. Also, it should
be renamed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... it should be renamed to login.sh

- Create a Kubernetes solution for ClusterRoleBindings, ClusterRoles, and
Projects using the clients.
- Convert all the client commands to API [calls](https://github.com/openshift/ansible-service-broker/search?p=1&q=%22oc%22&type=&utf8=%E2%9C%93).
- Convert any broker tools to target either OpenShift or Kubernetes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a first list of the tools that need to change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

Copy link
Contributor

@shawn-hurley shawn-hurley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also would like to see a cluster.Client interface or something similar.

The main issue with packaging is that folks using Kubernetes may only want the
Kubernetes client. The package ```origin-clients``` installs both the OpenShift
and Kubernetes clients. If this the container size isn't an issue, then there
may not be a problem using ```origin-clients```.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin package is what @jmrodri is referring to.


## Work Items
### Ansible-service-broker
- Split out all Cluster logic into a ```pkg/cluster/openshift.go``` and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want two clients to share the same package namespace? If this is too in weeds, please just this, but I am little confused on how separate the proposed clients are.
Options:
We have a kubernetes client that does 80% of the stuff. Then the openshift client has an embedded kubernetes type?
Or are they going to be completely separate objects that each contain their own logic for each action?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want two clients to share the same package namespace

Yes.

are they going to be completely separate objects that each contain their own logic for each action?

What I'm thinking we do is create an abstraction where the broker has only once code path for things like serviceaccounts, rolebindings, ect... The broker makes a call to the client which handles whether the action needs run with either the openshift or kubernetes client/API. Kubernetes and openshift will be in different files in the same pkg namespace with different logic for the ~20% of overlapping calls. The result is: If I create a rolebinding and I'm on openshift, the client object will know we need to use the openshift api. If I create a pod in openshift, the client object will know to use the kubernetes api.

Good questions @shawn-hurley. Will add more detail to the doc.

@rthallisey rthallisey changed the title Spec: Kubernetes and COE agnostic support [Under Review] Spec: Kubernetes and COE agnostic support Aug 7, 2017

The Cluster object will recieve the request and call the correct API.
```diff
+ func Create(c interface{}, ns string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some method they both share that we could make an interface for? That would allow the compiler to make some guarantees.

Also, passing in the type of object, (either k8s or oc pod) to determine what to do, still requires logic in the caller to determine which type of object to use right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using c interface{} to accept any API type as a parameter so I don't think a normal interface will work. Another way I could do this is a struct to hold variables for all the API types.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we create methods to create each resource?

I guess I am wondering why we are making it so generic, I think that in this case, the caller would probably just prefer to create a pod and not care about the pod object itself or the implementation of how that pod is created.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I am wondering why we are making it so generic, I think that in this case, the caller would
probably just prefer to create a pod and not care about the pod object itself or the implementation
of how that pod is created.

Create is very generic. I can make it more specific with Create<resource>. I agree, being more specific will be easier.

By being specific, we could do a few things. The caller can create a pod object and pass it to CreatePod or the caller can pass information to Create<resource>. I like the idea of abstracting the whole thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am arguing that we take the values that we need to create a pod and create a function that will take those values and both create the pod struct as well as call the method to whichever client.

The client is responsible for dealing with

  1. kube/oc specific errors making them client package errors
  2. kube/oc specific structs making the callers unaware of anything specific about the cluster we are running in
  3. Making a public(to our broker) API that will allow the broker to create the resources it needs. This is not a generic client that is being imported, it is very specific for just our broker IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated it see the latest spec.

Kubernetes client. The package ```origin-clients``` installs both the OpenShift
and Kubernetes clients. If this the container size isn't an issue, then there
may not be a problem using ```origin-clients```.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we annotate that an APB requires OpenShift and can not run on Kubernetes. Say it creates a Route or uses a DeploymentConfig?

I'm assuming any APB that runs on Kubernetes will be able to run on OpenShift.
Yet if an APB truly requires OpenShift (Route, DeploymentConfig, etc) it must have OpenShift and cannot run on pure k8s.

Would we want to allow an APB to downgrade itself based on environment?
i.e., it's written to deploy to OpenShift if available and use a DeploymentConfig, but if on K8S it follows a different path in the APB?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good questions @jwmatthews.

How do we annotate that an APB requires OpenShift and can not run on Kubernetes. Say it
creates a Route or uses a DeploymentConfig?

I think this is up to the user and we need to document 'Creating an APB' very well so it's clear where openshift and kubernetes differ. Looking at this as an APB writer, if I'm using Kubernetes, I'm going to write my apb with the resources I know Kubernetes uses. Ditto OpenShift. The case I'm concerned about is someone who didn't write the APB trying to use it with the wrong cluster. But I think we make it clear by having maybe a separate playbook directory, git repo, or top level directory for Kubernetes and OpenShift APBs.

I'm assuming any APB that runs on Kubernetes will be able to run on OpenShift.

Not 100% sure. What I'm aware of that could be problematic is the ClusterRoleBinding resource. But, that resource is mapped to the OpenShift security policy where as in Kubernetes it's Rbac. So I think it will work, but function different.

Either way, I think we should discourage this behaviour and treat the playbooks and the clusters as completely independent entities.

Would we want to allow an APB to downgrade itself based on environment?

No. Let's keep them separate. I think to have success here requires clean separation of the playbooks so we communicate to the user that didn't write the playbook what it's used for.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, that resource is mapped to the OpenShift security policy where as in Kubernetes it's Rbac. So I think it will work, but function different.

From my testing, RBAC entities are entirely ignored by OpenShift. It's very deceptive, because I was able to create RBAC *RoleBindings that appeared to have created absolutely fine (because they did), so my expectation was that they were, well...doing what role bindings do, but they were not from an openshift perspective. I am very hesitant to say this will work.

Either way, I think we should discourage this behaviour and treat the playbooks and the clusters as completely independent entities.

+1. I think fundamentally we should encourage good design practice around this. Organize logic in such a way where shared logic can be passed around with things like roles, and encapsulate pieces that are specific to certain clusters.

WRT annotation, I would like to see something in the apb spec that tells the broker what COE a given APB supports. I think the broker should validate early, know what cluster it's able to deploy to and what APBs are possible to be deployed to that cluster. That way the broker can say if !clusterSupports(apb) { handleUnsupported() } else { continue }

Copy link
Contributor Author

@rthallisey rthallisey Aug 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WRT annotation, I would like to see something in the apb spec that tells the broker what
COE a given APB supports. I think the broker should validate early, know what cluster it's
able to deploy to and what APBs are possible to be deployed to that cluster. That way the
broker can say if !clusterSupports(apb) { handleUnsupported() } else { continue }

@eriknelson good idea.

What if we create a field in apb.yaml for Cluster: (OpenShift/Kubernetes).
From the broker, we read the specs and identify which specs will work on the
current cluster and ignore the specs that don't work.


### Phase One
Infrastructure setup - Make sure there's a repeatable infrastracture setup
and begin transitioning broker code.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 Want to be sure team has a shared common way to run a script and get a working environment to develop/debug/test in.

The community has two tools for CI: Jenkins and Travis. When adding
Kubernetes support to the broker, the upstream Travis CI job should test the
broker and catalog on top of the upstream COE, Kubernetes. Jenkins will become
the downstream CI testing on OpenShift.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have Travis only test Kubernetes support?

If we could also do a sanity check with oc cluster up like we do now that'd be good feedback to have with a PR.

Essentially have the gate checking both K8S and OCP behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have Travis only test Kubernetes support?

We can have travis test both.

+ if OpenShift {
+ OCcli.CoreV1().ClusterRoleBinding(ns).Create(roleBindingM)
+ } elif Kubernetes {
+ k8scli.CoreV1().ClusterRoleBinding(ns).Create(roleBindingM)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a mechanism in Go where we can have the OCP Object reuse the K8S implementation and just override/implement the deltas?

Opposed to needing to redeclare, if OCP/if K8S workflows

Assuming OCP workflow can reuse k8scli except for those cases where there are deltas in behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a mechanism in Go where we can have the OCP Object reuse the K8s implementation
and just override/implement the deltas?

From the broker's perspective, that's essentially what I'm doing. The broker calls Cluster.CreatePod() and has no idea how the pod is created, but gets back the pod information. The cluster pkg handles the abstraction and figures out what cli/api to call.

Opposed to needing to redeclare, if OCP/if K8S workflows

I agree that the way we did it before wasn't good, but these checks aren't 'branching' the broker code like before. The extend of the 'branch' only encompasses one line, the client/API call. And the broker won't know this is happening because it's completely abstracted. The design is similar to a plugin, where the plugin code is abstracted away from the core broker code and does whatever it wants under the covers. Also different from before, OpenShift will come from the config file instead broker core code and we'll verify it when we start the broker.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, albeit a bit differently than traditional virtual methods. Go chooses to use composition over inheritance. On a high level, it's all about sharing behavior and delegation of specifics to components that know better (also providing encapsulation). We've organized our registry code and adapters in a similar way, so there is precedence, and I think we can leverage a similar pattern.

Spitballing, we can have a parent COE object that exposes the set of methods the broker needs to run APB actions. I would take the RunPod abstraction even further to RunApbAction. The fact that APBs are actually run as pods is an implementation detail; also, it provides optionality to run an APB action in a completely different way, (in an environment where the concept of pods don't even exist). Broker operates here on a higher level as well, calling into coe.RunApbAction(ActionProvision, params).

Broker can build a coe runner via factory method, which is responsible for constructing a coe runner based on the current, concrete cluster the broker is configured to target: runner := NewCOERunner(ClusterTypeKubernetes). Inside this, we can construct a parent COE with a clusterDelegate ClusterDelegate field, where ClusterDelegate is an interface sitting in front of cluster specifics. We can implement a ClusterDelegate for kubernetes and for openshift, and this is where the specialized behavior belongs.

Pseudocode:

type ClusterDelegate interface {
    SetupPermissions()
}

func (r *COERunner) RunApbAction(...) {
    r.sharedBehavior(...)
    r.clusterDelegate.SetupPermissions()
}

In this case, we know we need to SetupPermissions for all cluster types, they just need to be done in a different way, which the delegates define themselves. For similar cases, think about what's actually getting achieved where differences arise, abstract and declare that behavior (SetupPermissions) on the ClusterDelegate, and then allow the specific implementations to define what it actually means to SetupPermissions.

More food for thought, this very much reminds me of PIMPL from the c++ world, or the Bridge Pattern:
http://en.cppreference.com/w/cpp/language/pimpl
https://en.wikipedia.org/wiki/Bridge_pattern

@jmrodri
Copy link
Contributor

jmrodri commented Aug 8, 2017

@rthallisey @shawn-hurley @eriknelson I setup a meeting for 11am tomorrow to discuss this. I found it worked well with registries and filtering.

Copy link
Contributor

@eriknelson eriknelson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is pretty much there, I would like to have a design conversation and strategy for how we're going to be organizing the logic. We did this for multiple registries and it worked out really well.

@rthallisey
Copy link
Contributor Author

@jmrodri Sounds good. Can you post something on the ML about the discussion you scheduled?

@eriknelson
Copy link
Contributor

photo_2017-08-09_12-27-19


```diff
bindable: false
+ cluster: kubernetes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to track the version of the cluster type as well in the APB
Assuming some APBs may only work with k8s 1.6, others 1.7+ etc

### New APB Level Directory
Add a new directory layer into each APB.

mediawiki123-apb/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you surround these directory listings in ```

@eriknelson
Copy link
Contributor

Example of the runner pattern, important types introduced are the InputBundle, COERunner, COEDelegate, and the concrete delegate types.

https://gist.github.com/eriknelson/4c2c29ea107e6e197fb03ea7abe19a8d

Runnable: https://play.golang.org/p/Rv1ktceNEj

@rthallisey
Copy link
Contributor Author

I think this is good to go since this covers the major design topics. If anything is left to add I think it will be minor and we can deal with it on the fly.

@rthallisey rthallisey changed the title [Under Review] Spec: Kubernetes and COE agnostic support Spec: Kubernetes and COE agnostic support Aug 9, 2017
Copy link
Contributor

@eriknelson eriknelson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See request for Cluster -> Delegate.

+ DestroySandbox()
+}

+struct Cluster {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question since this was discussed as a Delegate: Do we care about runtimes that are not kubernetes or openshift? For example: vanilla docker? IMO, the answer is "No, but we should design for the possibility" since we've already had interest expressed from outside the team. With that in mind, are these other runtimes always going to be "Clusters"? I don't think it would be appropriate to call docker that.

My request would be to keep this as the runner's Delegate, curious how other folks feel about that. Otherwise, looks great. Thanks for writing this all up @rthallisey.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eriknelson that's true, Docker would not be a cluster. Something like Delegate wfm or maybe Runtime?

@eriknelson eriknelson merged commit 0d59553 into openshift:master Aug 10, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants