Skip to content

Custom controller framework #4

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

Closed
mhausenblas opened this issue Nov 7, 2019 · 10 comments
Closed

Custom controller framework #4

mhausenblas opened this issue Nov 7, 2019 · 10 comments
Assignees

Comments

@mhausenblas
Copy link
Contributor

mhausenblas commented Nov 7, 2019

This design issue is about how custom controllers are written in ASO. The goal is to select a widely used framework that has a low barrier to contributing to ASO. Usage of requirements key words as per RFC 2119.

Scope

In scope for this issue is the selection of the custom controller framework, that is, a Go-based framework supporting the creation of custom controllers used in ASO. Out of scope for this issue is the question how the business logic (managing AWS services) in the custom controllers is implemented, see #5 for this.

Input

Relevant candidates:

Input from roadmap issue aws/containers-roadmap#456:

Requirements

  1. The custom controller framework MUST support Go.
  2. The custom controller framework SHOULD be well-documented, well-maintained, and allow for easy on-boarding.
@daviddyball
Copy link
Contributor

I'd vote for kubebuilder. My company moved from operator-sdk to kubebuilder very early on and it was just a smoother process. We went from one barely working operator to 4 fully functioning controllers (one of them our internal implementation of ASO as a controller 😛)

I believe it is a younger project to operator-sdk, but it actually feels more stable and implements things in a "Kubernetes" way, whereas the Operator SDK defines it's own jargon and concepts that don't translate to the Kubernetes world very well IMHO.

kubebuilder is also a K8s SIG project, so support is pretty solid and well supported.

@cpoole
Copy link

cpoole commented Nov 12, 2019

+1 for kubebuilder my experience mirrors @daviddyball

kubebuilder feels much more kube native and I expect will see significantly more support and growth in the next year

@jaypipes
Copy link
Collaborator

My vote is obviously with kubebuilder as well. The trick for ASO is putting some scaffolding in place where we can a) generate the kubebuilder commands for each exposes AWS service API (basically, we need a "generator generator" :) and b) keep the CRDs and controller implementation for the various exposed AWS service APIs up to date as either upstream aws-sdk-go or the AWS APIs themselves change.

@pires
Copy link

pires commented Dec 17, 2019

kubebuilder has some rough edges, and so does controller-runtime (CT) which kubebuilder relies on. That said, it is a project backed by a couple sig-apimachinery people, so I trust it to gain more attention (and love) as its usage grows.
As an example, CT/kubebuilder are not yet prime-time for CRD v1.

@jaypipes jaypipes self-assigned this Dec 30, 2019
jaypipes referenced this issue in jaypipes/aws-controllers-k8s Dec 31, 2019
Adds a document discussing options for code generation and a diagram
with how a multi-phase hybrid custom+controller-runtime generator might
work.

Issue #4
jaypipes referenced this issue in jaypipes/aws-controllers-k8s Dec 31, 2019
Adds a document discussing options for code generation and a diagram
with how a multi-phase hybrid custom+controller-runtime generator might
work.

Issue #4
jaypipes referenced this issue in jaypipes/aws-controllers-k8s Jan 14, 2020
Adds a document discussing options for code generation and a diagram
with how a multi-phase hybrid custom+controller-runtime generator might
work.

Issue #4
@mhausenblas mhausenblas pinned this issue Jan 17, 2020
@mhausenblas mhausenblas mentioned this issue Jan 23, 2020
Closed
@negz
Copy link

negz commented Feb 28, 2020

Hi folks! I'm the tech lead for https://github.com/crossplane/stack-aws - a project very similar to what it sounds like you're planning with the Service Operator. We went with controller-runtime and have been quite happy with our choice. Rather than take a pure code generation/scaffolding approach we built a series of generic reconcilers that reconcile a "shape" (or duck type) of Kubernetes custom resource. These are packaged in crossplane-runtime, and could be reused by a project like the Service Operator if you so desired.

The general pattern is that there's a "reconciler scaffold" that handles all the parts of reconciling a Kubernetes CR with an AWS API that are mostly the same from API to API. This reconciler wraps an ExternalClient, such that the task of the entity implementing each controller is narrowed to satisfying some CRUD methods;

type ExternalClient interface {
	// Observe the external resource the supplied Managed resource represents,
	// if any. Observe implementations must not modify the external resource,
	// but may update the supplied Managed resource to reflect the state of the
	// external resource.
	Observe(ctx context.Context, mg resource.Managed) (ExternalObservation, error)

	// Create an external resource per the specifications of the supplied
	// Managed resource. Called when Observe reports that the associated
	// external resource does not exist.
	Create(ctx context.Context, mg resource.Managed) (ExternalCreation, error)

	// Update the external resource represented by the supplied Managed
	// resource, if necessary. Called unless Observe reports that the
	// associated external resource is up to date.
	Update(ctx context.Context, mg resource.Managed) (ExternalUpdate, error)

	// Delete the external resource upon deletion of its associated Managed
	// resource. Called when the managed resource has been deleted.
	Delete(ctx context.Context, mg resource.Managed) error
}

Our ElastiCache ReplicationGroup controller is a good example of a reconciler for a moderately complex AWS API that uses this pattern.

@negz
Copy link

negz commented Feb 28, 2020

@jaypipes PS the thoughts on code generation you captured in #15 matches our thinking around the subject almost exactly. Would you be interested in collaborating on something both projects could use?

jaypipes referenced this issue in jaypipes/aws-controllers-k8s Apr 3, 2020
Adds a new `ack-generate` CLI tool that can generate the Go files for
CRD types. The `ack-generate types` command accepts an OpenAPI3/Swagger
document, parses out the top-level resources in the API and constructs
the files with Go structs representing the CRDs/Kinds in the API.

```
$ go run cmd/ack-generate/main.go types < /tmp/sns.swagger.yaml
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//     http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type Endpoint struct {
	Attributes map[string]string `json:"Attributes,omitempty"`
	EndpointArn string `json:"EndpointArn,omitempty"`
}

type Subscription struct {
	Endpoint string `json:"Endpoint,omitempty"`
	Owner string `json:"Owner,omitempty"`
	Protocol string `json:"Protocol,omitempty"`
	SubscriptionArn string `json:"SubscriptionArn,omitempty"`
	TopicArn string `json:"TopicArn,omitempty"`
}

type KMSOptInRequired struct {
	Message string `json:"message,omitempty"`
}

type Tag struct {
	Key string `json:"Key,omitempty"`
	Value string `json:"Value,omitempty"`
}

type MessageAttributeValue struct {
	BinaryValue string `json:"BinaryValue,omitempty"`
	DataType string `json:"DataType,omitempty"`
	StringValue string `json:"StringValue,omitempty"`
}

// PlatformEndpointSpec defines the desired state of PlatformEndpoint
type PlatformEndpointSpec struct {
	// The Arn attr is on all AWS service API CRs. It represents the Amazon
	// Resource Name for the object. CRs of this Kind that are created without
	// an Arn attr will be created by the controller. CRs of this Kind that
	// are created with a non-nil Arn attr are considered by the controller to
	// already exist in the backend AWS service API.
	Arn string `json:"arn,omitempty"`
	Attributes map[string]string `json:"Attributes,omitempty"`
	CustomUserData string `json:"CustomUserData,omitempty"`
	PlatformApplicationArn string `json:"PlatformApplicationArn,omitempty"`
	Token string `json:"Token,omitempty"`
}

// PlatformEndpointStatus defines the observed state of PlatformEndpoint
type PlatformEndpointStatus struct {
	EndpointArn string `json:"EndpointArn,omitempty"`
}

// PlatformEndpoint is the Schema for the PlatformEndpoints API
type PlatformEndpoint struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec   PlatformEndpointSpec   `json:"spec,omitempty"`
	Status PlatformEndpointStatus `json:"status,omitempty"`
}

// PlatformEndpointList contains a list of PlatformEndpoint
type PlatformEndpointList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []PlatformEndpoint `json:"items"`
}

// TopicSpec defines the desired state of Topic
type TopicSpec struct {
	// The Arn attr is on all AWS service API CRs. It represents the Amazon
	// Resource Name for the object. CRs of this Kind that are created without
	// an Arn attr will be created by the controller. CRs of this Kind that
	// are created with a non-nil Arn attr are considered by the controller to
	// already exist in the backend AWS service API.
	Arn string `json:"arn,omitempty"`
	Attributes map[string]string `json:"Attributes,omitempty"`
	Name string `json:"Name,omitempty"`
	Tags []*Tags `json:"Tags,omitempty"`
}

// TopicStatus defines the observed state of Topic
type TopicStatus struct {
	TopicArn string `json:"TopicArn,omitempty"`
}

// Topic is the Schema for the Topics API
type Topic struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec   TopicSpec   `json:"spec,omitempty"`
	Status TopicStatus `json:"status,omitempty"`
}

// TopicList contains a list of Topic
type TopicList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []Topic `json:"items"`
}

// PlatformApplicationSpec defines the desired state of PlatformApplication
type PlatformApplicationSpec struct {
	// The Arn attr is on all AWS service API CRs. It represents the Amazon
	// Resource Name for the object. CRs of this Kind that are created without
	// an Arn attr will be created by the controller. CRs of this Kind that
	// are created with a non-nil Arn attr are considered by the controller to
	// already exist in the backend AWS service API.
	Arn string `json:"arn,omitempty"`
	Attributes map[string]string `json:"Attributes,omitempty"`
	Name string `json:"Name,omitempty"`
	Platform string `json:"Platform,omitempty"`
}

// PlatformApplicationStatus defines the observed state of PlatformApplication
type PlatformApplicationStatus struct {
	PlatformApplicationArn string `json:"PlatformApplicationArn,omitempty"`
}

// PlatformApplication is the Schema for the PlatformApplications API
type PlatformApplication struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec   PlatformApplicationSpec   `json:"spec,omitempty"`
	Status PlatformApplicationStatus `json:"status,omitempty"`
}

// PlatformApplicationList contains a list of PlatformApplication
type PlatformApplicationList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []PlatformApplication `json:"items"`
}

func init() {
	SchemeBuilder.Register(&PlatformEndpoint{}, &PlatformEndpointList{})
	SchemeBuilder.Register(&Topic{}, &TopicList{})
	SchemeBuilder.Register(&PlatformApplication{}, &PlatformApplicationList{})
}
```

Issue #4

Produces the types.go file that can be fed into `controller-gen crds`
@mhausenblas mhausenblas unpinned this issue May 28, 2020
@jaypipes
Copy link
Collaborator

@jaypipes PS the thoughts on code generation you captured in #15 matches our thinking around the subject almost exactly. Would you be interested in collaborating on something both projects could use?

@negz so sorry for being so uncommunicative about this! Not sure if you've been following along with our progress on the mvp branch, but we've been slogging along slowly but surely. Last Friday I pushed up a pull request that adds a good chunk of the controller implementation generation. Would be awesome to get your comments on it. I can certainly see our teams collaborating on a bunch of common runtime stuff, for the record!

@negz
Copy link

negz commented Jun 24, 2020

Nice, thanks @jaypipes! I'll take a look this week. Would you be interested in setting up a call at some point to swap notes?

@jaypipes
Copy link
Collaborator

absolutely @negz I would be more than happy to do that. My schedule next week is pretty open. Catch me on Slack and we'll schedule a time. :)

@jaypipes
Copy link
Collaborator

Closing this out. We ended up going with a custom code generator + controller-gen tooling.

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

No branches or pull requests

6 participants