# 换马甲：十分钟 Helm 变 Operator

[Operator 是一种将传统运维思路转换为 Kubernetes CRD 控制的方法](https://blog.fleeto.us/post/operator-for-kubernetes/)，利用 CRD 对软件部署和配置进行定义，整个部署和管理过程在 Kubernetes 角度上来看，都是一个可见、可审计的行为，这无疑对运维工作是大有裨益的。[CoreOS 也提供了 Operator Framwork](https://github.com/operator-framework) 用于进行 Operator 的开发，不过门槛还是稍高的。如果放低要求，是否能有一个折衷方案？

CoreOS 为[最近加入 CNCF 的 Helm](https://www.helm.sh/helm-enters-the-cncf/index.html) 提供了一个小工具，可以无需编程操作，较为方便的将 Helm Chart 转换为 Operator，并将原有的 `values.yaml` 更替为 CR 资源进行操作，对于无状态应用的部署流程，可以说是比较便利了。下面就随便举个例子，看看这马甲是怎么换的。

> 目前版本相当幼稚，看看就好了。

## 准备工作

首先是一个可操作的 Kubernetes 集群，要求版本为 1.9+。
接下来要有一个可操作的 Helm 客户端（无需 Tiller 部署），用于下载 Chart。

使用 git 获取 [Helm app operator kit](https://github.com/operator-framework/helm-app-operator-kit)：

~~~shell
$ git clone https://github.com/operator-framework/helm-app-operator-kit.git
Cloning into 'helm-app-operator-kit'...
...
Resolving deltas: 100% (58/58), done.
~~~

## 镜像构建

下载一个实验 Chart **并解压**：

~~~shell
$ helm fetch stable/memcached
$ tar xf memcached-2.2.0.tgz
$ ls -la
...
-rw-r--r--   1 dustise  wheel    680  8 30 11:50 Dockerfile
...
drwxr-xr-x  10 dustise  wheel    320  8 30 11:50 helm-app-operator
drwxr-xr-x   7 dustise  wheel    224  8 30 11:52 memcached
...
~~~

这里的 Dockerfile 可以略微关注一下：

~~~dockerfile
FROM golang:1.10 as builder
...
RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
...
RUN CGO_ENABLED=0 GOOS=linux go build -o bin/operator cmd/helm-app-operator/main.go
...
FROM alpine:3.6
...
COPY --from=builder /go/src/github.com/operator-framework/helm-app-operator-kit/helm-app-operator/bin/operator /operator
...
CMD ["/operator"]
~~~

一个典型的分段构建过程。在 Go 环境中生成可执行文件用于最终镜像的执行。

使用 Dockerfile 进行构建：

~~~shell
docker build -t your-repo:25000/helm/memcached-operator \
    --build-arg HELM_CHART=memcached \
    --build-arg API_VERSION=anywhere.io/v1alpha1 \
    --build-arg KIND=memcached .
~~~

- **HELM_CHART**：我们之前解压的 Chart 目录。
- **API_VERSION**：即将用到的自定义资源的 API 组和版本。
- **KIND**：自定义资源名称。

Docker 构建完成之后，将新镜像 Push 到 Kubernetes 可访问的镜像库中。

## Operator 部署

构建成功之后，进入 `helm-app-operator/deploy` 目录，要部署 Operator，首先要修改几个文件。

### rbac.yaml

这是 Operator 运行所需的权限设置文件，根据前面的配置，我们需要给他加入两个权限：namespace 以及新建的 CRD 的操作权限。

> 这里的 RoleBinding 只是绑定到了 default 命名空间的 default ServiceAccount，如果要给 Operator Pod 单独赋权，就要对 `subject` 进行修改。

文件编辑结束后，就可以使用 `kubectl apply` 提交到集群运行。

~~~yaml
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: helm-app-operator
rules:
...
  - secrets
  - namespaces
  verbs:
  - "*"
- apiGroups:
  - apps
  resources:
  - deployments
  - daemonsets
  - replicasets
  - statefulsets
  verbs:
  - "*"
...
- apiGroups:
  - anywhere.io
  resources:
  - "*"
  verbs:
  - "*"
~~~

### crd.yaml

接下来就是自定义资源的定义了。这里需要和前面我们制定的 API 结构相吻合

~~~yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: memcacheds.anywhere.io # 资源名 + 组名
spec:
  group: anywhere.io # 组
  names:
    kind: memcached # 对象
    listKind: memcachedList #列表
    plural: memcacheds # 复数形式
    singular: memcached # 单数形式
  scope: Namespaced
  version: v1alpha1 # 版本
~~~

同样的，使用 `kubectl apply` 提交这一定义给 Kubernetes 集群。

### operator.yaml

这个文件很简单，是一个 Deployment 对象定义，修改一下镜像名即可，如果 `rbac.yaml` 中修改了绑定账号，这里也需要修改 Operator 的运行账号。

最后，用 `kubectl apply deploy/operator.yaml`，即可启动 Operator 的运行了。可以使用 `kubectl get po -w` 获取运行状况。

## 创建应用实例

`deploy` 目录中还有另外一个文件：`cr.yaml`，就是我们的自定义资源实例文件。过去需要在 Chart 的 `values.yaml` 中编写的内容，现在需要在这里完成了。通过 `helm inspect stable/memcached` 命令，可以看到其中支持的参数列表。这里我们可以设置一下，用来创建一个 3 实例的集群：

~~~yaml
apiVersion: anywhere.io/v1alpha1
kind: memcached
metadata:
  name: memcached-yy
  labels:
    app: example-app
spec:
  replicaCount: 3
~~~

使用 `kubectl apply` 提交之后，可以看到集群上开始创建这一实例：

~~~shell
$ kubectl get po
...
helm-app-operator-memcached-yy-0      1/1       Running            0          10h
helm-app-operator-memcached-yy-1      1/1       Running            0          10h
helm-app-operator-memcached-yy-2      1/1       Running            0          10h
...
~~~

> 这里 Operator Pod 可能会崩溃，删除即可正常工作😄。

### 查询实例情况：

~~~yaml
$ kubectl get memcached
NAME           CREATED AT
memcached-r    10h
memcached-yy   10h
~~~

### 删除实例

~~~yaml
$ kubectl delete memcached memcached-yy
memcached.anywhere.io "memcached-yy" deleted
~~~

再次使用 `kubectl get po`，会发现对应 Pod 已经删除。

## 结语

CoreOS 再次提供了一个有趣的方向，有效的降低了 Operator 的入门门槛。但是这一方案除了成熟度相当不足之外，Helm 本身对运维的支持其实也是非常弱的，对有状态应用是无论如何不能使用这种方式来进行运维的。——马甲，只是马甲。
