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

feat: add source code and related k8s manifest #6

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 14 additions & 0 deletions balloon-controller/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:1.21.0-alpine as builder

WORKDIR /balloon-controller

COPY ./ ./

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -mod vendor -o balloon-operator ./

FROM alpine:3.18

COPY --from=builder /balloon-controller/balloon-operator /bin/

ENTRYPOINT [ "/bin/balloon-operator" ]

119 changes: 119 additions & 0 deletions balloon-controller/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
> 该部分内容提供了 **balloon-controller** 的具体实现,我们将自定义控制器打包成容器镜像并上传到镜像仓库。通过在 kubernetes 集群中部署相关的 manifest 让自定义控制器真正的运行起来。
>
> 本部分的代码与[《从零实现Kubernetes自定义控制器》](https://caozhuozi.github.io/crd-controller-from-scratch/)系列内容以及 [balloon-controller](https://github.com/caozhuozi/balloon-controller) 仓库中的内容基本相同。
> 添加了些 log 相关的内容便于调试。
>
> 我们假定你已经对容器镜像构建、kubernetes 权限管理等内容有基本的了解。

# Prerequisite

将 **balloon-controller** 部署到 kubernetes 集群,或是上手进行编码工作,你需要准备下面的环境:

- 可用的 kubernetes 集群,用以部署我们的自定义控制器
- Golang 开发环境,用以编写控制器的代码
- Docker,用以打包和上传自定义控制器的镜像

# Install

自定义控制器的镜像可以使用我们已经打包好的,也可以参照后面的内容自己[打包镜像](#build-image)。

### Step1: 部署权限配置文件

**balloon-controller** 在 `default` 命名空间使用默认的服务账户,我们需要为其配置权限使其能够对我们自定义的 `Balloon` 资源进行一些基本的 `watch/get/list/update` 的操作:

```shell
# cd crd-controller-from-scratch/balloon-controller/manifest/
# kubectl apply -f role-binding-sa.yaml
role.rbac.authorization.k8s.io/balloon-controller-role created
rolebinding.rbac.authorization.k8s.io/balloon-controller-rolebinding created
```

### Step2: 部署 CRD 和 Balloon 资源

```shell
# kubectl apply -f balloon-crd.yaml
customresourcedefinition.apiextensions.k8s.io/balloons.book.dong.io created
# kubectl apply -f balloon.yaml
balloon.book.dong.io/my-balloon created
# kubectl get balloons
NAME STATUS
my-balloon
```

气球目前还没有到达释放时间,因此它的 `STATUS` 为空。为了更好的看到效果,你需要根据实际情况修改 manifest 中气球的释放时间:

```yaml
apiVersion: "book.dong.io/v1"
kind: Balloon
metadata:
name: my-balloon
namespace: default
spec:
# 根据实际时间修改
releaseTime: "2023-09-07T17:24:00+08:00"
```

### Step3: 部署 balloon-controller

目前集群中存在我们自定义的气球资源,但是集群中没有控制器来对其执行特定的动作。我们部署气球控制器使其可以控制气球的“释放”:

```shell
# kubectl apply -f balloon-controller-deploy.yaml
deployment.apps/balloon-controller created
```

等待 Pod Ready:

```shell
# kubectl get pod
NAME READY STATUS RESTARTS AGE
balloon-controller-7884b6489b-z8nv7 1/1 Running 0 97s
```

在到了气球的释放时间后,查看气球状态已变成“释放”:

```shell
# kubectl logs balloon-controller-7884b6489b-pr8dd
2023/09/08 06:52:43 balloon controller started successfully!
2023/09/08 06:52:43 The balloon: my-balloon is created.
2023/09/08 06:52:43 The balloon: my-balloon is updated.
2023/09/08 06:53:03 releasing balloon: my-balloon
2023/09/08 06:53:03 The balloon: my-balloon is updated.
2023/09/08 06:53:03 balloon: my-balloon is released, status is Released
# kubectl get balloons
NAME STATUS
my-balloon Released
```

# Build Image

由于本系列教程的主要目的是实现一个极为简单的自定义控制器,控制器代码中对于一些可能遇到的需要处理的错误,或是更为复杂和高级的内容并没有做相应的实现。

这些内容有兴趣的读者可以自行实现,为你的气球添加更多更有趣的玩法。

由于在 Dockerfile 中使用 `go mod` 拉取一些代码库会出现问题,因此我们使用 vendor 形式在容器中编译。在打包之前,使用 `go mod vendor` 更新引用包到本地,然后构建镜像时一同将所有代码依赖放到容器中进行编译。

接下来,你就可以构建自己的 **balloon-controller** 镜像并按照仓库的具体要求打上 tag,再推送到自己的仓库中。

```shell
# cd crd-controller-from-scratch/balloon-controller/
# docker build -t {$YourImageName}:{$Version} .
# docker login {$YourRepository}
# docker push {$YourImageName}:{$Version}
```

最后,将 manifest 中的镜像替换成你的控制器,部署你的气球,等待控制器工作。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
...
spec:
...
spec:
containers:
- name: balloon-controller
image: {$YourImageName}:{$Version}
imagePullPolicy: IfNotPresent
```
36 changes: 36 additions & 0 deletions balloon-controller/api/deepcopy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package api

import "k8s.io/apimachinery/pkg/runtime"

func (in *Balloon) DeepCopyObject() runtime.Object {
if in == nil {
return nil
}

out := new(Balloon)
*out = *in
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)

return out
}

func (in *BalloonList) DeepCopyObject() runtime.Object {
if in == nil {
return nil
}

out := new(BalloonList)

*out = *in
in.ListMeta.DeepCopyInto(&out.ListMeta)

if in.Items != nil {
in, out := &in.Items, &out.Items
for i := range *in {
c := (*in)[i].DeepCopyObject().(*Balloon)
*out = append(*out, *c)
}
}

return out
}
22 changes: 22 additions & 0 deletions balloon-controller/api/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package api

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
)

var GroupVersion = schema.GroupVersion{
Group: "book.dong.io",
Version: "v1",
}

func init() {
scheme.Scheme.AddKnownTypes(
GroupVersion,
&Balloon{},
&BalloonList{},
)

metav1.AddToGroupVersion(scheme.Scheme, GroupVersion)
}
40 changes: 40 additions & 0 deletions balloon-controller/api/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package api

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

type BalloonSpec struct {
ReleaseTime string `json:"releaseTime"`
}

type BalloonStatus struct {
Status string `json:"status"`
}

type Balloon struct {
// metav1.TypeMeta 实现了 runtime.Object 的第一个方法 GetObjectKind()
// 当我们引入后 只需要再实现 DeepCopyObject() 就能实现 runtime.Object 接口了
// 我们可以认为 TypeMeta 代表 CRD 最基本的 group version kind 信息
metav1.TypeMeta `json:",inline"`

// Balloon 作为单体需要实现 metav1.Object 接口的
// 而 metav1.ObjectMeta 则实现了这个接口
// 对于 ObjectMeta 可以理解为 CRD 的 name namespace annotation 等信息
metav1.ObjectMeta `json:"metadata,omitempty"`

// Spec 就是具体的 yaml 中配置信息了 对应 spec 字段
Spec BalloonSpec `json:"spec"`

// status 字段涉及到子资源这一概念
// 用户可以写入/更改资源的期望状态 spec 但不应更改资源的 status 字段
// 控制器可以写入/更改资源的实际状态 status 但不应更改资源的 spec 字段
Status BalloonStatus `json:"status"`
}

type BalloonList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

Items []Balloon `json:"items"`
}
109 changes: 109 additions & 0 deletions balloon-controller/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package client

import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"

"balloon-controller/api"
)

type BalloonClient struct {
restClient rest.Interface
ns string
}

func setConfigDefaults(config *rest.Config) error {
gv := api.GroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()

return nil
}

func NewBalloonClient(c *rest.Config, namespace string) (*BalloonClient, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}

client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}

return &BalloonClient{
restClient: client,
ns: namespace,
}, nil
}

func (c *BalloonClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*api.Balloon, error) {
result := api.Balloon{}
err := c.restClient.
Get().
Namespace(c.ns).
Resource("balloons").
Name(name).
VersionedParams(&opts, scheme.ParameterCodec).
Do(ctx).
Into(&result)

return &result, err
}

func (c *BalloonClient) List(ctx context.Context, opts metav1.ListOptions) (*api.BalloonList, error) {
result := api.BalloonList{}
err := c.restClient.
Get().
Namespace(c.ns).
Resource("balloons").
VersionedParams(&opts, scheme.ParameterCodec).
Do(ctx).
Into(&result)

return &result, err
}

func (c *BalloonClient) Create(ctx context.Context, balloon *api.Balloon) (*api.Balloon, error) {
result := &api.Balloon{}
err := c.restClient.
Post().
Namespace(c.ns).
Resource("balloons").
Body(balloon).
Do(ctx).
Into(result)

return result, err
}

func (c *BalloonClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
opts.Watch = true

return c.restClient.
Get().
Namespace(c.ns).
Resource("balloons").
VersionedParams(&opts, scheme.ParameterCodec).
Watch(ctx)
}

func (c *BalloonClient) UpdateStatus(ctx context.Context, balloon *api.Balloon, opts metav1.UpdateOptions) (result *api.Balloon, err error) {
result = &api.Balloon{}

err = c.restClient.Put().
Namespace(c.ns).
Resource("balloons").
Name(balloon.Name).
SubResource("status").
VersionedParams(&opts, scheme.ParameterCodec).
Body(balloon).
Do(ctx).
Into(result)

return result, err
}
36 changes: 36 additions & 0 deletions balloon-controller/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module balloon-controller

go 1.21

require (
k8s.io/apimachinery v0.28.1
k8s.io/client-go v0.28.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
golang.org/x/net v0.13.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.28.1 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
Loading