#kubernetes #k8s #operator

roperator

Easily create Kubernetes Operators with Rust

8 releases

✓ Uses Rust 2018 edition

0.2.1 Apr 26, 2020
0.2.0 Apr 25, 2020
0.1.5 Mar 2, 2020
0.1.4 Feb 26, 2020
0.1.2 Dec 26, 2019

#7 in Configuration

Download history 15/week @ 2020-01-18 4/week @ 2020-02-01 6/week @ 2020-02-08 37/week @ 2020-02-15 73/week @ 2020-02-22 20/week @ 2020-02-29 17/week @ 2020-03-07 11/week @ 2020-03-14 20/week @ 2020-03-21 18/week @ 2020-03-28 7/week @ 2020-04-04 37/week @ 2020-04-11 4/week @ 2020-04-18 29/week @ 2020-04-25 9/week @ 2020-05-02

91 downloads per month

MIT/Apache

285KB
6K SLoC

Roperator

roperator crates.io

Roperator lets you easily write Kubernetes Operators in Rust. Roperator handles all the mechanics and plumbing of watching and updating your resources. All you have to do is to write a function that returns the desired state of your resources. Roperator was heavily inspired by the excellent Metacontroller project, which it seems is no longer maintained.

Declarative Operator

The goal of this project is to make it really easy to write operators for the vast majority of common cases. Roperator focuses on the case where you have a parent Custom Resource Definition, and for each parent you'll want a set of corresponding child resources. For example, you might have a CRD that describes your application, and for each instance of that Custom Resource, you'll want to create a potentially complex set of child resources.

The core of your operator is a sync function that gets passed a snapshot view of the parent custom resource and any existing children in a SyncRequest struct. This function will simply return the desired set of children for the given parent, as well as a JSON object that should be set as the status of the parent. Your sync function does not need to directly communicate with the Kubernetes API server in any way. Roperator will handle watching all of the resources, invoking your sync function whenever it's needed, and updating the child resources and the parent status.

The EchoServer example is a good first example to look at to see how everythign fits together end to end.

Master branch status

The master branch includes work for the upcoming 0.2.0 release. This includes a few minor breaking changes. If you want to submit a bugfix PR against the 0.1.x branch, then use release-0.1 as the base branch for that.

Dependencies

  • Rust 1.39 or later
  • Openssl libraries and headers. Roperator uses the rust openssl crate. You can check out their docs on building and linking to openssl here

Getting Started

First add the dependency:

# Cargo.toml
[dependencies]
roperator = "*"

[dev-dependencies]
roperator = { version = "*", features = ["testkit"] }

Operator Configuration

The first thing you'll need is a configuration object that specifies the relationships between the CRD that will act as the parent and the types of resources that may be created as children.

use roperator::prelude::{k8s_types, K8sType, OperatorConfig, ChildConfig, run_operator};
/// The type of our parent Custom Resource. This must match the fields provided in the CRD
pub static PARENT_TYPE: &K8sType = &K8sType {
    api_version: "example.com/v1alpha1",
    kind: "MyResource",
    plural_kind: "myresources",
};

fn main() {
    let operator_config = OperatorConfig::new("my-operator-name", PARENT_TYPE)
            .with_child(k8s_types::core::v1::Pod, ChildConfig::recreate())
            .with_child(k8s_types::core::v1::Service, ChildConfig::replace());
    //.... you can have as many child types as you want, but you need to specify all of the types that you may create ahead of time

    let my_handler = MyHandler; // we'll look at Handlers further down

    run_operator(operator_config, my_handler).expect("failed to run operator");
}

Handler

The main logic of your operator is inside your Handler implementation. The main function you need to implement is Handler::sync, which accepts a SyncRequest and returns a SyncResponse. The Hander trait is implemented automatically for all Fn(&SyncRequest) -> Result<SyncResponse, Error>, but you can also implement it for your own type.

use roperator::prelude::{Handler, SyncRequest, SyncResponse, FinalizeResponse, Error};

struct MyHandler;
impl Handler for MyHandler {
    fn sync(&self, request: &SyncRequest) -> Result<SyncResponse, Error> {
        // some json object that describes the status of the parent. Typically, this will be determined just based on the
        // statuses of the child resources
        let parent_status = determine_parent_status(request);

        // returns all of the desired kubernetes resources that should correspond to this parent
        let children = determine_desired_child_resources(request);
        Ok(SyncResponse {
            status: parent_status,
            children
        })
    }
}

Handlers may optionally implement the finalize function. This function gets called when then parent resource is being deleted, and allows you to clean up any external resources that may have been created. Roperator does its best to ensure that finalize will get invoked at least once per parent that is being deleted. It is not possible to guarantee that this will happen, though, since Kuberntes allows clients to force the deletion of resources without waiting for finalizers.

fn finalize(&self, request: &SyncRequest) -> Result<FinalizeResponse, Error> {
    // We return a boolean that says whether the finalization was successful or not. If we return false,
    // then roperator will continue to regularly re-try your finalize function until it succeeds.
    // As soon as we return `true`, roperator will remove itself from the finalizers list of your parent
    // resource and allow the deletion to proceed.
    let cleanup_succeeded: bool = do_cleanup(request);
    let parent_status = determine_parent_status(request);

    Ok(FinalizeResponse {
        status: parent_status,
        finalized: cleanup_succeeded,
    })
}

The Path to 1.0

Roperator is still young, and not yet "production grade". The APIs will also be unstable until we reach a 1.0 milestone. It would really benefit from feedback from early adopters, so please file an issue or submit a Pull Request. The goal is to quickly identify the things that need to be addressed before the 1.0 release, and focus on producing a production grade 1.0 release as soon as is reasonable.

Some of the things that will definitely need addressed prior to the 1.0 release are:

  • Build out a much more complete test suite
  • Polish up the API for accessing child resources from a SyncRequest
  • Settle on a more consistent and simple API for the TestKit
  • Settle on a simpler API for running multiple operators in the same process

Contributions are Welcome

Contributions are welcome and appreciated. Please check out the Contributors Guide for more information.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Roperator by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~14MB
~301K SLoC