$ mkdir -p $HOME/projects/memcached-operator
基于 Java 的 Operator SDK 仅为技术预览功能。技术预览功能不受 Red Hat 生产服务级别协议 (SLA) 的支持,并且可能功能不完整。Red Hat 不建议在生产环境中使用它们。这些功能可让客户提前访问即将推出的产品功能,从而能够在开发过程中测试功能并提供反馈。 有关 Red Hat 技术预览功能的支持范围的更多信息,请参见 技术预览功能支持范围。 |
Operator 开发人员可以利用 Operator SDK 中的 Java 编程语言支持来构建一个面向 Memcached(一个分布式键值存储)的示例 Java 驱动的 Operator,并管理其生命周期。
Red Hat 支持的 Operator SDK CLI 工具版本(包括 Operator 项目的相关脚手架和测试工具)已弃用,并计划在未来版本的 OpenShift Container Platform 中删除。Red Hat 将在此功能的当前版本生命周期内提供错误修复和支持,但此功能将不再接收增强功能,并将从未来的 OpenShift Container Platform 版本中删除。 不建议使用 Red Hat 支持的 Operator SDK 版本创建新的 Operator 项目。拥有现有 Operator 项目的 Operator 作者可以使用 OpenShift Container Platform 4.17 发布的 Operator SDK CLI 工具版本来维护其项目并创建针对较新版本的 OpenShift Container Platform 的 Operator 版本。 以下与 Operator 项目相关的基础镜像 *未* 被弃用。这些基础镜像的运行时功能和配置 API 仍受支持,用于错误修复和解决 CVE。
有关 OpenShift Container Platform 中已弃用或删除的主要功能的最新列表,请参阅 OpenShift Container Platform 发行说明中的 *已弃用和删除的功能* 部分。 有关不受支持的、社区维护的 Operator SDK 版本的信息,请参见 Operator SDK (Operator Framework)。 |
此过程是使用 Operator Framework 的两个核心部分完成的
operator-sdk
CLI 工具和 java-operator-sdk
库 API
在集群上安装、升级和基于角色的访问控制 (RBAC) Operator
本教程比 面向 Java 驱动的 Operator 的 Operator SDK 入门 更详细。 |
使用 Operator SDK CLI 创建名为 memcached-operator
的项目。
为项目创建一个目录
$ mkdir -p $HOME/projects/memcached-operator
切换到该目录
$ cd $HOME/projects/memcached-operator
使用 quarkus
插件运行 operator-sdk init
命令来初始化项目
$ operator-sdk init \
--plugins=quarkus \
--domain=example.com \
--project-name=memcached-operator
使用 Operator SDK CLI 创建自定义资源定义 (CRD) API 和控制器。
运行以下命令以创建 API
$ operator-sdk create api \
--plugins=quarkus \(1)
--group=cache \(2)
--version=v1 \(3)
--kind=Memcached (4)
1 | 将 plugin 标志设置为 quarkus 。 |
2 | 将 group 标志设置为 cache 。 |
3 | 将 version 标志设置为 v1 。 |
4 | 将 kind 标志设置为 Memcached 。 |
运行 tree
命令以查看文件结构
$ tree
.
├── Makefile
├── PROJECT
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── example
│ ├── Memcached.java
│ ├── MemcachedReconciler.java
│ ├── MemcachedSpec.java
│ └── MemcachedStatus.java
└── resources
└── application.properties
6 directories, 8 files
为 Memcached
自定义资源 (CR) 定义 API。
编辑作为 create api
过程一部分生成的以下文件
更新 MemcachedSpec.java
文件中的以下属性以定义 Memcached
CR 的所需状态
public class MemcachedSpec {
private Integer size;
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
}
更新 MemcachedStatus.java
文件中的以下属性以定义 Memcached
CR 的观察状态
以下示例说明了节点状态字段。建议您在实践中使用 典型的状态属性。 |
import java.util.ArrayList;
import java.util.List;
public class MemcachedStatus {
// Add Status information here
// Nodes are the names of the memcached pods
private List<String> nodes;
public List<String> getNodes() {
if (nodes == null) {
nodes = new ArrayList<>();
}
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}
更新 Memcached.java
文件以定义 Memcached API 的模式,该模式扩展到 MemcachedSpec.java
和 MemcachedStatus.java
文件。
@Version("v1")
@Group("cache.example.com")
public class Memcached extends CustomResource<MemcachedSpec, MemcachedStatus> implements Namespaced {}
在使用 MemcachedSpec
和 MemcachedStatus
文件定义 API 后,您可以生成 CRD 清单。
在memcached-operator
目录下运行以下命令来生成CRD:
$ mvn clean install
验证target/kubernetes/memcacheds.cache.example.com-v1.yml
文件中CRD的内容,如下例所示:
$ cat target/kubernetes/memcacheds.cache.example.com-v1.yaml
# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: memcacheds.cache.example.com
spec:
group: cache.example.com
names:
kind: Memcached
plural: memcacheds
singular: memcached
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
properties:
spec:
properties:
size:
type: integer
type: object
status:
properties:
nodes:
items:
type: string
type: array
type: object
type: object
served: true
storage: true
subresources:
status: {}
创建新的API和控制器后,您可以实现控制器逻辑。
将以下依赖项添加到pom.xml
文件中:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
在此示例中,请将生成的控制器文件MemcachedReconciler.java
替换为以下示例实现:
MemcachedReconciler.java
示例package com.example;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.fabric8.kubernetes.api.model.ContainerBuilder;
import io.fabric8.kubernetes.api.model.ContainerPortBuilder;
import io.fabric8.kubernetes.api.model.LabelSelectorBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.OwnerReferenceBuilder;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodSpecBuilder;
import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.api.model.apps.DeploymentSpecBuilder;
import org.apache.commons.collections.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class MemcachedReconciler implements Reconciler<Memcached> {
private final KubernetesClient client;
public MemcachedReconciler(KubernetesClient client) {
this.client = client;
}
// TODO Fill in the rest of the reconciler
@Override
public UpdateControl<Memcached> reconcile(
Memcached resource, Context context) {
// TODO: fill in logic
Deployment deployment = client.apps()
.deployments()
.inNamespace(resource.getMetadata().getNamespace())
.withName(resource.getMetadata().getName())
.get();
if (deployment == null) {
Deployment newDeployment = createMemcachedDeployment(resource);
client.apps().deployments().create(newDeployment);
return UpdateControl.noUpdate();
}
int currentReplicas = deployment.getSpec().getReplicas();
int requiredReplicas = resource.getSpec().getSize();
if (currentReplicas != requiredReplicas) {
deployment.getSpec().setReplicas(requiredReplicas);
client.apps().deployments().createOrReplace(deployment);
return UpdateControl.noUpdate();
}
List<Pod> pods = client.pods()
.inNamespace(resource.getMetadata().getNamespace())
.withLabels(labelsForMemcached(resource))
.list()
.getItems();
List<String> podNames =
pods.stream().map(p -> p.getMetadata().getName()).collect(Collectors.toList());
if (resource.getStatus() == null
|| !CollectionUtils.isEqualCollection(podNames, resource.getStatus().getNodes())) {
if (resource.getStatus() == null) resource.setStatus(new MemcachedStatus());
resource.getStatus().setNodes(podNames);
return UpdateControl.updateResource(resource);
}
return UpdateControl.noUpdate();
}
private Map<String, String> labelsForMemcached(Memcached m) {
Map<String, String> labels = new HashMap<>();
labels.put("app", "memcached");
labels.put("memcached_cr", m.getMetadata().getName());
return labels;
}
private Deployment createMemcachedDeployment(Memcached m) {
Deployment deployment = new DeploymentBuilder()
.withMetadata(
new ObjectMetaBuilder()
.withName(m.getMetadata().getName())
.withNamespace(m.getMetadata().getNamespace())
.build())
.withSpec(
new DeploymentSpecBuilder()
.withReplicas(m.getSpec().getSize())
.withSelector(
new LabelSelectorBuilder().withMatchLabels(labelsForMemcached(m)).build())
.withTemplate(
new PodTemplateSpecBuilder()
.withMetadata(
new ObjectMetaBuilder().withLabels(labelsForMemcached(m)).build())
.withSpec(
new PodSpecBuilder()
.withContainers(
new ContainerBuilder()
.withImage("memcached:1.4.36-alpine")
.withName("memcached")
.withCommand("memcached", "-m=64", "-o", "modern", "-v")
.withPorts(
new ContainerPortBuilder()
.withContainerPort(11211)
.withName("memcached")
.build())
.build())
.build())
.build())
.build())
.build();
deployment.addOwnerReference(m);
return deployment;
}
}
此示例控制器为每个Memcached
自定义资源 (CR)运行以下协调逻辑:
如果不存在,则创建Memcached部署。
确保部署大小与Memcached
CR规范中指定的大小匹配。
使用memcached
Pod的名称更新Memcached
CR状态。
接下来的小节将解释示例实现中的控制器如何监视资源以及如何触发协调循环。您可以跳过这些小节,直接跳转到运行Operator。
每个控制器都有一个带有Reconcile()
方法的协调器对象,该方法实现协调循环。协调循环将传递Deployment
参数,如下例所示:
Deployment deployment = client.apps()
.deployments()
.inNamespace(resource.getMetadata().getNamespace())
.withName(resource.getMetadata().getName())
.get();
如下例所示,如果Deployment
为null
,则需要创建部署。创建Deployment
后,您可以确定是否需要协调。如果不需要协调,则返回UpdateControl.noUpdate()
的值,否则,返回`UpdateControl.updateStatus(resource)`的值。
if (deployment == null) {
Deployment newDeployment = createMemcachedDeployment(resource);
client.apps().deployments().create(newDeployment);
return UpdateControl.noUpdate();
}
获取Deployment
后,获取当前副本数和所需副本数,如下例所示:
int currentReplicas = deployment.getSpec().getReplicas();
int requiredReplicas = resource.getSpec().getSize();
如果currentReplicas
与requiredReplicas
不匹配,则必须更新Deployment
,如下例所示:
if (currentReplicas != requiredReplicas) {
deployment.getSpec().setReplicas(requiredReplicas);
client.apps().deployments().createOrReplace(deployment);
return UpdateControl.noUpdate();
}
以下示例显示如何获取Pod列表及其名称:
List<Pod> pods = client.pods()
.inNamespace(resource.getMetadata().getNamespace())
.withLabels(labelsForMemcached(resource))
.list()
.getItems();
List<String> podNames =
pods.stream().map(p -> p.getMetadata().getName()).collect(Collectors.toList());
检查是否创建了资源并使用Memcached资源验证pod名称。如果这些条件中的任何一个不匹配,请执行协调,如下例所示:
if (resource.getStatus() == null
|| !CollectionUtils.isEqualCollection(podNames, resource.getStatus().getNodes())) {
if (resource.getStatus() == null) resource.setStatus(new MemcachedStatus());
resource.getStatus().setNodes(podNames);
return UpdateControl.updateResource(resource);
}
labelsForMemcached
labelsForMemcached
是一个返回要附加到资源的标签映射的实用程序。
private Map<String, String> labelsForMemcached(Memcached m) {
Map<String, String> labels = new HashMap<>();
labels.put("app", "memcached");
labels.put("memcached_cr", m.getMetadata().getName());
return labels;
}
createMemcachedDeployment
createMemcachedDeployment
方法使用fabric8 DeploymentBuilder
类。
private Deployment createMemcachedDeployment(Memcached m) {
Deployment deployment = new DeploymentBuilder()
.withMetadata(
new ObjectMetaBuilder()
.withName(m.getMetadata().getName())
.withNamespace(m.getMetadata().getNamespace())
.build())
.withSpec(
new DeploymentSpecBuilder()
.withReplicas(m.getSpec().getSize())
.withSelector(
new LabelSelectorBuilder().withMatchLabels(labelsForMemcached(m)).build())
.withTemplate(
new PodTemplateSpecBuilder()
.withMetadata(
new ObjectMetaBuilder().withLabels(labelsForMemcached(m)).build())
.withSpec(
new PodSpecBuilder()
.withContainers(
new ContainerBuilder()
.withImage("memcached:1.4.36-alpine")
.withName("memcached")
.withCommand("memcached", "-m=64", "-o", "modern", "-v")
.withPorts(
new ContainerPortBuilder()
.withContainerPort(11211)
.withName("memcached")
.build())
.build())
.build())
.build())
.build())
.build();
deployment.addOwnerReference(m);
return deployment;
}
您可以使用Operator SDK CLI通过三种方式构建和运行您的Operator:
作为Go程序在集群外部本地运行。
作为部署在集群上运行。
捆绑您的Operator并使用Operator Lifecycle Manager (OLM)在集群上部署。
您可以将Operator项目作为Go程序在集群外部运行。这对于开发目的很有用,可以加快部署和测试速度。
运行以下命令来编译Operator:
$ mvn clean install
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.193 s
[INFO] Finished at: 2021-05-26T12:16:54-04:00
[INFO] ------------------------------------------------------------------------
运行以下命令将CRD安装到默认命名空间:
$ oc apply -f target/kubernetes/memcacheds.cache.example.com-v1.yml
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.example.com created
创建一个名为rbac.yaml
的文件,如下例所示:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: memcached-operator-admin
subjects:
- kind: ServiceAccount
name: memcached-quarkus-operator-operator
namespace: <operator_namespace>
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: ""
运行以下命令通过应用rbac.yaml
文件来授予memcached-quarkus-operator-operator
集群管理员权限:
$ oc apply -f rbac.yaml
输入以下命令来运行Operator:
$ java -jar target/quarkus-app/quarkus-run.jar
|
使用以下命令应用memcached-sample.yaml
文件:
$ kubectl apply -f memcached-sample.yaml
memcached.cache.example.com/memcached-sample created
运行以下命令以确认Pod已启动:
$ oc get all
NAME READY STATUS RESTARTS AGE
pod/memcached-sample-6c765df685-mfqnz 1/1 Running 0 18s
您可以将Operator项目作为部署在您的集群上运行。
运行以下make
命令来构建和推送Operator镜像。修改以下步骤中的IMG
参数以引用您有权访问的存储库。您可以在Quay.io等存储库站点获取用于存储容器的帐户。
构建镜像:
$ make docker-build IMG=<registry>/<user>/<image_name>:<tag>
Operator 的 SDK 生成的 Dockerfile 明确引用了非 AMD64 架构的 `GOARCH=amd64` 用于 `go build`。这可以修改为 `GOARCH=$TARGETARCH`。Docker 将自动将环境变量设置为 `–platform` 指定的值。对于 Buildah,需要使用 `–build-arg`。更多信息,请参见多架构。 |
将镜像推送到存储库:
$ make docker-push IMG=<registry>/<user>/<image_name>:<tag>
镜像的名称和标签(例如 |
运行以下命令将CRD安装到默认命名空间:
$ oc apply -f target/kubernetes/memcacheds.cache.example.com-v1.yml
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.example.com created
创建一个名为rbac.yaml
的文件,如下例所示:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: memcached-operator-admin
subjects:
- kind: ServiceAccount
name: memcached-quarkus-operator-operator
namespace: <operator_namespace>
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: ""
|
运行以下命令来部署Operator:
$ make deploy IMG=<registry>/<user>/<image_name>:<tag>
运行以下命令通过应用前面步骤中创建的rbac.yaml
文件来授予memcached-quarkus-operator-operator
集群管理员权限:
$ oc apply -f rbac.yaml
运行以下命令以验证Operator是否正在运行:
$ oc get all -n default
NAME READY UP-TO-DATE AVAILABLE AGE
pod/memcached-quarkus-operator-operator-7db86ccf58-k4mlm 0/1 Running 0 18s
运行以下命令来应用memcached-sample.yaml
并创建memcached-sample
pod:
$ oc apply -f memcached-sample.yaml
memcached.cache.example.com/memcached-sample created
运行以下命令以确认Pod已启动:
$ oc get all
NAME READY STATUS RESTARTS AGE
pod/memcached-quarkus-operator-operator-7b766f4896-kxnzt 1/1 Running 1 79s
pod/memcached-sample-6c765df685-mfqnz 1/1 Running 0 18s
Operator捆绑格式是Operator SDK和Operator Lifecycle Manager (OLM)的默认打包方法。您可以通过使用Operator SDK将Operator项目构建和推送为捆绑镜像,从而使您的Operator准备好用于OLM。
在开发工作站上安装Operator SDK CLI
已安装OpenShift CLI (oc
) v4.17+
使用Operator SDK初始化的Operator项目
在您的Operator项目目录中运行以下make
命令来构建和推送您的Operator镜像。修改以下步骤中的IMG
参数以引用您有权访问的存储库。您可以在Quay.io等存储库站点获取用于存储容器的帐户。
构建镜像:
$ make docker-build IMG=<registry>/<user>/<operator_image_name>:<tag>
Operator 的 SDK 生成的 Dockerfile 明确引用了非 AMD64 架构的 `GOARCH=amd64` 用于 `go build`。这可以修改为 `GOARCH=$TARGETARCH`。Docker 将自动将环境变量设置为 `–platform` 指定的值。对于 Buildah,需要使用 `–build-arg`。更多信息,请参见多架构。 |
将镜像推送到存储库:
$ make docker-push IMG=<registry>/<user>/<operator_image_name>:<tag>
通过运行make bundle
命令创建您的Operator捆绑清单,该命令将调用多个命令,包括Operator SDK generate bundle
和bundle validate
子命令。
$ make bundle IMG=<registry>/<user>/<operator_image_name>:<tag>
Operator的捆绑清单描述了如何显示、创建和管理应用程序。make bundle
命令在您的Operator项目中创建以下文件和目录:
一个名为bundle/manifests
的捆绑清单目录,其中包含一个ClusterServiceVersion
对象。
一个名为bundle/metadata
的捆绑元数据目录。
config/crd
目录中的所有自定义资源定义 (CRD)。
一个Dockerfile bundle.Dockerfile
。
然后,这些文件将使用operator-sdk bundle validate
自动验证,以确保磁盘上的捆绑表示正确。
通过运行以下命令来构建和推送您的捆绑镜像。OLM使用索引镜像来使用Operator捆绑包,该镜像引用一个或多个捆绑镜像。
构建捆绑镜像。使用注册表、用户命名空间和您打算推送镜像的镜像标签的详细信息设置BUNDLE_IMG
。
$ make bundle-build BUNDLE_IMG=<registry>/<user>/<bundle_image_name>:<tag>
推送捆绑镜像:
$ docker push <registry>/<user>/<bundle_image_name>:<tag>
操作符生命周期管理器 (OLM) 可帮助您在 Kubernetes 集群上安装、更新和管理操作符及其关联服务的生命周期。OLM 在 OpenShift Container Platform 中默认安装,并作为 Kubernetes 扩展运行,因此您可以使用 Web 控制台和 OpenShift CLI (oc
) 执行所有操作符生命周期管理功能,无需任何其他工具。
操作符捆绑包格式是 Operator SDK 和 OLM 的默认打包方法。您可以使用 Operator SDK 快速在 OLM 上运行捆绑包镜像,以确保其正常运行。
在开发工作站上安装Operator SDK CLI
已构建并推送到注册表的 Operator 捆绑包镜像
安装在基于 Kubernetes 的集群上的 OLM(如果您使用的是 apiextensions.k8s.io/v1
CRD,则版本为 v1.16.0 或更高版本,例如 OpenShift Container Platform 4.17)
使用具有 cluster-admin
权限的帐户,通过 oc
登录到集群
输入以下命令在集群上运行 Operator
$ operator-sdk run bundle \(1)
-n <namespace> \(2)
<registry>/<user>/<bundle_image_name>:<tag> (3)
1 | run bundle 命令创建一个有效的基于文件的目录,并使用 OLM 在您的集群上安装 Operator 捆绑包。 |
2 | 可选:默认情况下,该命令会在您 ~/.kube/config 文件中当前活动的项目中安装 Operator。您可以添加 -n 标志来设置安装的不同命名空间范围。 |
3 | 如果您没有指定镜像,则该命令使用 quay.io/operator-framework/opm:latest 作为默认索引镜像。如果您指定了镜像,则该命令使用捆绑包镜像本身作为索引镜像。 |
从 OpenShift Container Platform 4.11 开始, |
此命令执行以下操作
创建一个引用您的捆绑包镜像的索引镜像。索引镜像是不透明且短暂的,但准确地反映了如何在生产环境中将捆绑包添加到目录。
创建一个指向您的新索引镜像的目录源,这使得 OperatorHub 可以发现您的 Operator。
通过创建 OperatorGroup
、Subscription
、InstallPlan
和所有其他必需资源(包括 RBAC)将您的 Operator 部署到您的集群。
请参阅 基于 Java 的 Operator 的项目布局,了解 Operator SDK 创建的目录结构。
如果配置了集群范围的出站代理,集群管理员可以覆盖代理设置或为在 Operator 生命周期管理器 (OLM) 上运行的特定 Operator 注入自定义 CA 证书。