Skip to content

Commit

Permalink
Merge pull request #37 from coryodaniel/refactor-on-k8s-client
Browse files Browse the repository at this point in the history
Refactor on new k8s client
  • Loading branch information
coryodaniel committed Mar 11, 2019
2 parents 5c0ff51 + 56fa458 commit be27f46
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 306 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Expand Up @@ -7,9 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `mix bonny.gen.manifest --local` for building manifests w/o a Deployment for
local testing
- `cluster_name: :default` config options. Now uses [k8s](https://github.com/coryodaniel/k8s) cluster registration configuration.

### Changed

- Replaced `HTTPoison` with [k8s](https://github.com/coryodaniel/k8s).
- Removed `Bypass` from test suite

### Removed

- Removed `Impl.parse_metadata/1`.
- Removed `kubeconf_file` and `kubeconf_opts` config options

## [0.3.0] - 2019-03-04

Expand All @@ -27,4 +39,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- mix task: controller generator
- mix task: dockerfile generator
- mix task: k8s manifest generator

3 changes: 0 additions & 3 deletions Makefile
Expand Up @@ -14,6 +14,3 @@ analyze:

docs:
mix docs

i:
BONNY_CONFIG_FILE=~/.kube/config MIX_ENV=test iex -S mix
106 changes: 42 additions & 64 deletions README.md
@@ -1,6 +1,5 @@
![Bonny](./banner.png "Bonny")


[![Build Status](https://travis-ci.org/coryodaniel/bonny.svg?branch=master)](https://travis-ci.org/coryodaniel/bonny)
[![Coverage Status](https://coveralls.io/repos/github/coryodaniel/bonny/badge.svg?branch=master)](https://coveralls.io/github/coryodaniel/bonny?branch=master)
[![Hex.pm](http://img.shields.io/hexpm/v/bonny.svg?style=flat)](https://hex.pm/packages/bonny)
Expand All @@ -12,10 +11,10 @@ Extend the Kubernetes API and implement CustomResourceDefinitions lifecycles in

If Kubernetes CRDs and controllers are new to you, read up on the [terminology](#terminology).

*Tutorials and Examples:*
_Tutorials and Examples:_

* HelloOperator Tutorial Part: [1](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-1-34ccb2ea0b4d) [2](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-2-efdf8e422085) [3](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-3-fdfc8b8cc843)
* HelloOperator [source code](https://gitub.com/coryodaniel/hello_operator)
- HelloOperator Tutorial Part: [1](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-1-34ccb2ea0b4d) [2](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-2-efdf8e422085) [3](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-3-fdfc8b8cc843)
- HelloOperator [source code](https://gitub.com/coryodaniel/hello_operator)

## Installation

Expand All @@ -32,9 +31,19 @@ end

### Configuration

The only configuration parameter required is `controllers`:
Bonny uses the [k8s client](https://github.com/coryodaniel/k8s) under the hood.

The only configuration parameters required are `:bonny` `controllers` and a `:k8s` cluster:

```elixir

config :k8s,
clusters: %{
default: %{ # `default` here must match `cluster_name` below
conf: "~/.kube/config"
}
}

config :bonny,
# Add each CRD Controller module for this operator to load here
# Defaults to none. This *must* be set.
Expand All @@ -44,6 +53,9 @@ config :bonny,
MyApp.Controllers.V1.Memcached
],

# K8s.Cluster to use, defaults to :default
cluster_name: :default,

# Set the Kubernetes API group for this operator.
# This can be overwritten using the @group attribute of a controller
group: "your-operator.example.com",
Expand All @@ -65,18 +77,12 @@ config :bonny,
resources: %{
limits: %{cpu: "200m", memory: "200Mi"},
requests: %{cpu: "200m", memory: "200Mi"}
},

# Kubernetes YAML config, defaults to the service account of the pod
kubeconf_file: "",

# Defaults to "current-context" if a config file is provided, override user, cluster. or context here
kubeconf_opts: []
}
```

## Example Operators built with Bonny

* [Hello Operator](https://github.com/coryodaniel/hello_operator)
- [Hello Operator](https://github.com/coryodaniel/hello_operator)

## Bonny Generators

Expand All @@ -100,32 +106,32 @@ You can specify the version flag to create a new version of a controller. Bonny
mix bonny.gen.controller Widget widgets --version v2alpha1
```

*Note:* The one restriction with versions is that they will be camelized into a module name.
_Note:_ The one restriction with versions is that they will be camelized into a module name.

Open up your controller and add functionality for your resoures lifecycle:

* Add
* Modify
* Delete
- Add
- Modify
- Delete

Each controller can create multiple resources.

For example, a *todo app* controller could deploy a `Deployment` and a `Service`.
For example, a _todo app_ controller could deploy a `Deployment` and a `Service`.

Your operator can also have multiple controllers if you want to support multiple resources in your operator!

Check out the two test controllers:

* [Cog](./test/support/cog.ex)
* [Widget](./test/support/widget.ex)
- [Cog](./test/support/cog.ex)
- [Widget](./test/support/widget.ex)

### Generating a dockerfile

The following command will generate a dockerfile *for your operator*. This will need to be pushed to a docker repository that your kubernetes cluster can access.
The following command will generate a dockerfile _for your operator_. This will need to be pushed to a docker repository that your kubernetes cluster can access.

Again, this Dockerfile is for your operator, not for the pods your operator may deploy.

You can skip this step when developing by running your operator *external* to the cluster.
You can skip this step when developing by running your operator _external_ to the cluster.

```shell
mix bonny.gen.dockerfile
Expand All @@ -139,30 +145,30 @@ docker push ${BONNY_IMAGE}:latest

This will generate the entire manifest for this operator including:

* CRD manifests
* RBAC
* Service Account
* Operator Deployment
- CRD manifests
- RBAC
- Service Account
- Operator Deployment

The operator manifest generator requires the `image` flag to be passed if you plan to deploy the operator in your cluster. This is the docker image URL of your operators docker image created by `mix bonny.gen.docker` above.

```shell
mix bonny.gen.manifest --image ${BONNY_IMAGE}
```

You may *omit* the `--image` flag if you want to generate a manifest _without the deployment_ so that you can develop locally running the operator outside of the cluster.
You may _omit_ the `--image` flag if you want to generate a manifest _without the deployment_ so that you can develop locally running the operator outside of the cluster.

**Note:** YAML output is JSON formatted YAML. Sorry, elixirland isn't fond of YAML :D

By default the manifest will generate the service account and deployment in the "default" namespace.

*To set the namespace explicitly:*
_To set the namespace explicitly:_

```shell
mix bonny.gen.manifest --out - -n test
```

*Alternatively you can apply it directly to kubectl*:
_Alternatively you can apply it directly to kubectl_:

```shell
mix bonny.gen.manifest --out - -n test | kubectl apply -f - -n test
Expand All @@ -172,24 +178,24 @@ mix bonny.gen.manifest --out - -n test | kubectl apply -f - -n test

TODO: Need to support validation / OpenAPI.

* https://github.com/coryodaniel/bonny/issues/9
* https://github.com/coryodaniel/bonny/issues/10
- https://github.com/coryodaniel/bonny/issues/9
- https://github.com/coryodaniel/bonny/issues/10

## Terminology

*[Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#custom-resources)*:
_[Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#custom-resources)_:

> A custom resource is an extension of the Kubernetes API that is not necessarily available on every Kubernetes cluster. In other words, it represents a customization of a particular Kubernetes installation.
*[CRD Custom Resource Definition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions)*:
_[CRD Custom Resource Definition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions)_:

> The CustomResourceDefinition API resource allows you to define custom resources. Defining a CRD object creates a new custom resource with a name and schema that you specify. The Kubernetes API serves and handles the storage of your custom resource.
*[Controller](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#custom-controllers)*:
_[Controller](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#custom-controllers)_:

> A custom controller is a controller that users can deploy and update on a running cluster, independently of the cluster’s own lifecycle. Custom controllers can work with any kind of resource, but they are especially effective when combined with custom resources. The Operator pattern is one example of such a combination. It allows developers to encode domain knowledge for specific applications into an extension of the Kubernetes API.
*Operator*:
_Operator_:

A set of application specific controllers deployed on Kubernetes and managed via kubectl and the Kubernetes API.

Expand All @@ -199,40 +205,12 @@ Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_do
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/bonny](https://hexdocs.pm/bonny).


## Testing

```elixir
mix test
```

### Starting an interactive test session

This will load two modules in the operator, `Widget` and `Cog`.

**Create the CRDs:**

```shell
kubectl apply -f ./test/support/crd.yaml
```

**Start the session:**

```elixir
BONNY_CONFIG_FILE=~/.kube/config MIX_ENV=test iex -S mix
```

The GenServers wait about 5 seconds to start watching.

**Trigger some events:**

```shell
kubectl apply -f ./test/support/widget.yaml
```

If log level is set to `debug` you will see the events get sent to the pod's logs.


## Operator Blog Posts

* [Why Kubernetes Operators are a game changer](https://blog.couchbase.com/kubernetes-operators-game-changer/)
- [Why Kubernetes Operators are a game changer](https://blog.couchbase.com/kubernetes-operators-game-changer/)
3 changes: 2 additions & 1 deletion config/config.exs
Expand Up @@ -4,9 +4,10 @@ if Mix.env() == :test do
config :logger, level: :error

config :bonny,
k8s_client: Bonny.K8sMockClient,
controllers: [Widget, Cog],
group: "example.com",
kubeconf_file: "./test/support/kubeconfig.yaml"
cluster_name: :test
end

if Mix.env() == :dev do
Expand Down
18 changes: 3 additions & 15 deletions lib/bonny/config.ex
Expand Up @@ -106,21 +106,9 @@ defmodule Bonny.Config do
end

@doc """
`K8s.Conf` configuration. This is used to sign HTTP requests to the Kubernetes API.
Bonny defaults to the service account of the pod if a cluster configuration is not provided.
`K8s.Cluster` name used for this operator. Defaults to `:default`
"""
def kubeconfig() do
config_path =
System.get_env("BONNY_CONFIG_FILE") || Application.get_env(:bonny, :kubeconf_file)

case config_path do
conf_path when is_binary(conf_path) ->
conf_opts = Application.get_env(:bonny, :kubeconf_opts, [])
K8s.Conf.from_file(conf_path, conf_opts)

_ ->
K8s.Conf.from_service_account()
end
def cluster_name() do
Application.get_env(:bonny, :cluster_name, :default)
end
end
52 changes: 14 additions & 38 deletions lib/bonny/crd.ex
Expand Up @@ -32,40 +32,29 @@ defmodule Bonny.CRD do
names: nil,
version: nil

@doc "Plural name of CRD"
@spec plural(Bonny.CRD.t()) :: binary
def plural(%Bonny.CRD{names: %{plural: plural}}), do: plural

@doc """
URL Path to list a CRD's resources
Plural name of CRD
*Namespaced CRD URL Path*
/apis/bonny.example.om/v1/namespaces/default/widgets
## Examples
*Cluster Resource URL Path & `--all-namespaces` path*
/apis/example.com/v1/widgets
"""
@spec list_path(Bonny.CRD.t()) :: binary
def list_path(crd = %CRD{}), do: base_path(crd)
iex> Bonny.CRD.plural(%Bonny.CRD{names: %{plural: "greetings"}, scope: :namespaced, group: "test", version: "v1"})
"greetings"
@spec watch_path(Bonny.CRD.t(), String.t() | integer) :: binary
def watch_path(crd = %CRD{}, resource_version) do
"#{base_path(crd)}?resourceVersion=#{resource_version}&watch=true"
end
"""
@spec plural(Bonny.CRD.t()) :: binary
def plural(%Bonny.CRD{names: %{plural: plural}}), do: plural

@doc """
URL path to read the specified CustomResourceDefinition
Gets group version from CRD spec
## Examples
*Namespaced CRD Resource URL Path*
/apis/example.com/v1/namespaces/default/widgets/test-widget
iex> Bonny.CRD.group_version(%Bonny.CRD{group: "hello.example.com", version: "v1", scope: :namespaced, names: %{}})
"hello.example.com/v1"
*Cluster CRD Resource URL Path & `--all-namespaces` path*
/apis/example.com/v1/widgets/test-widget
"""
@spec read_path(Bonny.CRD.t(), String.t()) :: binary
def read_path(crd = %CRD{}, name) do
"#{base_path(crd)}/#{name}"
end
@spec group_version(Bonny.CRD.t()) :: binary
def group_version(%Bonny.CRD{group: g, version: v}), do: "#{g}/#{v}"

@doc """
Generates the map equivalent of the Kubernetes CRD YAML manifest
Expand Down Expand Up @@ -100,17 +89,4 @@ defmodule Bonny.CRD do
spec: %{crd | scope: cased_scope}
}
end

defp base_path(%CRD{
scope: :namespaced,
version: version,
group: group,
names: %{plural: plural}
}) do
"/apis/#{group}/#{version}/namespaces/#{Bonny.Config.namespace()}/#{plural}"
end

defp base_path(%CRD{scope: :cluster, version: version, group: group, names: %{plural: plural}}) do
"/apis/#{group}/#{version}/#{plural}"
end
end
9 changes: 3 additions & 6 deletions lib/bonny/watcher.ex
Expand Up @@ -24,10 +24,7 @@ defmodule Bonny.Watcher do
end

def handle_info(:watch, state) do
{:ok, state} = Impl.get_resource_version(state)
Logger.debug(fn -> "Starting watch from resource version: #{state.resource_version}" end)
Impl.watch_for_changes(state, self())

{:noreply, state}
end

Expand All @@ -44,10 +41,10 @@ defmodule Bonny.Watcher do

@impl GenServer
def handle_info(%HTTPoison.AsyncChunk{chunk: chunk}, state = %Impl{}) do
chunk
|> Impl.parse_chunk()
|> Impl.dispatch(state.mod)
event = Impl.parse_chunk(chunk)
Impl.dispatch(event, state.controller)

state = Impl.set_resource_version(state, event)
{:noreply, state}
end

Expand Down

0 comments on commit be27f46

Please sign in to comment.