<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>operator | 伪架构师</title>
    <link>/tags/operator/</link>
      <atom:link href="/tags/operator/index.xml" rel="self" type="application/rss+xml" />
    <description>operator</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Mon, 13 Apr 2020 16:21:32 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>operator</title>
      <link>/tags/operator/</link>
    </image>
    
    <item>
      <title>自己的 Kubernetes 控制器（3）——改进和部署</title>
      <link>/post/your-own-k8s-controller-3/</link>
      <pubDate>Mon, 13 Apr 2020 16:21:32 +0800</pubDate>
      <guid>/post/your-own-k8s-controller-3/</guid>
      <description>

&lt;p&gt;我们在前面讲述了 Kubernetes 控制器的概念。简单说来控制器就是个控制回路，用来将当前状态协调到目标状态。第二篇使用 Java 实现了一个控制器。这一篇会讲讲如何部署控制器，以及如何对控制器进行改进。&lt;/p&gt;

&lt;h2 id=&#34;集群内外&#34;&gt;集群内外&lt;/h2&gt;

&lt;p&gt;在第一篇中提到过，控制器在集群内外都能运行，只要能够完成必要的通信过程就可以。缺省情况下，官方 Kubernetes 客户端和 Fabric8 客户端都会尝试使用 &lt;code&gt;~/.kube/config&lt;/code&gt; 配置中存储的凭据。也就是说只要使用 &lt;code&gt;kubectl&lt;/code&gt; 命令能访问集群，就能运行这个控制器。&lt;/p&gt;

&lt;p&gt;交付物可以是以下几种形式：独立的 JAR，应用服务器中部署的 WebApp，甚至是一个包含很多 Class 文件的目录。这种方法的缺点是，应该把所有与所选择的方法相关的常规任务都照顾到。&lt;/p&gt;

&lt;p&gt;另一方面，用容器化应用的方式在 Kubernetes 集群中运行会有很多好处：自动化、监控、伸缩、自愈等。如此看来，没有不容器化的道理。因此我们要给我们的控制器进行容器化。&lt;/p&gt;

&lt;h2 id=&#34;控制器的容器化&#34;&gt;控制器的容器化&lt;/h2&gt;

&lt;p&gt;给 Java 应用进行容器化的最直接方式就是使用 &lt;a href=&#34;https://github.com/GoogleContainerTools/jib&#34; target=&#34;_blank&#34;&gt;Jib 插件&lt;/a&gt;。这个插件在 Maven 和 Gradle 中可用，兼容于普通应用、Spring Boot 和 Micronaut 应用；它生成的镜像会分为不同的层次：最上层是业务类，下面则是依赖库。这种构建方式加快了更新镜像的构建速度：当业务更新时，只需要更换最上面的层就可以了。&lt;/p&gt;

&lt;p&gt;Jib 配置样例：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;plugin&amp;gt;
    &amp;lt;groupId&amp;gt;com.google.cloud.tools&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jib-maven-plugin&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.8.0&amp;lt;/version&amp;gt;
    &amp;lt;configuration&amp;gt;
        &amp;lt;from&amp;gt;
            &amp;lt;image&amp;gt;gcr.io/distroless/java:debug&amp;lt;/image&amp;gt;
        &amp;lt;/from&amp;gt;
        &amp;lt;to&amp;gt;
            &amp;lt;image&amp;gt;jvm-operator:${project.version}&amp;lt;/image&amp;gt;
        &amp;lt;/to&amp;gt;
    &amp;lt;/configuration&amp;gt;
    &amp;lt;executions&amp;gt;
        &amp;lt;execution&amp;gt;
            &amp;lt;phase&amp;gt;compile&amp;lt;/phase&amp;gt;
            &amp;lt;goals&amp;gt;
                &amp;lt;goal&amp;gt;dockerBuild&amp;lt;/goal&amp;gt;
            &amp;lt;/goals&amp;gt;
        &amp;lt;/execution&amp;gt;
    &amp;lt;/executions&amp;gt;
&amp;lt;/plugin&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;缺省镜像没有 Shell，为了方便调试，提供一个 &lt;code&gt;debug&lt;/code&gt; Tag&lt;/li&gt;
&lt;li&gt;目标镜像的标签来自于 POM&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;compile&lt;/code&gt; 阶段会运行插件。注意镜像并没有进行打包操作，因此 &lt;code&gt;package&lt;/code&gt; 阶段可以跳过&lt;/li&gt;
&lt;li&gt;可用的目标包括 &lt;code&gt;build&lt;/code&gt; 和 &lt;code&gt;dockerBuild&lt;/code&gt;。前者无需本地 Docker，并把镜像上传到 DockerHub；后者会把镜像构建到本地 Docker 中&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;到了这一步，写个 Kubernetes 配置就很容易了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;deploy.yml&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  namespace: jvmoperator
  name: custom-operator
spec:
  containers:
    - name: custom-operator
      image: jvm-operator:1.10
      imagePullPolicy: Never
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上边的代码段偷懒声明了一个简单的 &lt;code&gt;Pod&lt;/code&gt;。真实世界的配置会用 &lt;code&gt;Deployment&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl apply -f deploy.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;不幸的是，这个命令会失败，输出下列内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;java.net.ProtocolException: Expected HTTP 101 response but was &#39;403 Forbidden&#39;
  at okhttp3.internal.ws.RealWebSocket.checkResponse(RealWebSocket.java:229)
  at okhttp3.internal.ws.RealWebSocket$2.onResponse(RealWebSocket.java:196)
  at okhttp3.RealCall$AsyncCall.execute(RealCall.java:203)
  at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
  at java.lang.Thread.run(Thread.java:748)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;鉴权&#34;&gt;鉴权&lt;/h2&gt;

&lt;p&gt;这个错误仅在集群内运行时候发生，原因是权限不足。给 Kubernetes API 发送请求是个危险行为，缺省情况下每个请求都会返回错误。因此这个容器需要有合适的授权：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  namespace: jvmoperator
  name: operator-example
rules:
  - apiGroups:
      - &amp;quot;&amp;quot;
    resources:
      - pods
    verbs:
      - watch
      - create
      - delete
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: operator-service
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: operator-example
subjects:
  - kind: ServiceAccount
    name: operator-service
    namespace: jvmoperator
roleRef:
  kind: ClusterRole
  name: operator-example
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubernetes 中用 RBAC 的方式进行鉴权。这方面的主题比较复杂，想要细致学习，可以参考相关文档。&lt;/p&gt;

&lt;p&gt;提交上述代码后，这个 Pod 就能够使用新的 ServiceAccount 运行了——只要做一点简单的修改：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  namespace: jvmoperator
  name: custom-operator
spec:
  serviceAccountName: operator-service
  containers:
    - name: custom-operator
      image: jvm-operator:1.8
      imagePullPolicy: Never
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;容器化-jvm-应用的隐患&#34;&gt;容器化 JVM 应用的隐患&lt;/h2&gt;

&lt;p&gt;早期版本的 JVM 会返回主机的 CPU 和内存数量，而不是容器的。JVM 尝试占用不存在的内存，会导致 &lt;code&gt;OutOfMemoryError&lt;/code&gt;。Kubernetes 则会杀死行为异常的 Pod。如果被杀死 Pod 是  ReplicaSet 的一部分，就会新建一个 Pod。这个过程很不利联想。JDK 10 开始这个问题已经解决了（这个特性也被融合到 JDK 8 的新版本之中）。&lt;/p&gt;

&lt;p&gt;JVM 能够根据工作负载来调整应用程序的编译代码，这是优于静态编译的原生可执行程序的。JVM 需要大量的额外内存来实现这一点。而且 JVM 的启动时间相当长。由于自适应编译后的代码需要时间，所以在启动后的一段时间内，性能都不会符合要求。这也是为什么在 JVM 上的性能指标总是要在较长的预热时间后再进行测量的原因。最后，与原生可执行文件相比，容器的大小要大得多，因为它嵌入了 JVM 本身。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;REPOSITORY            TAG          IMAGE ID            CREATED             SIZE
jvm-operator          1.8          bdaa419c75e2        50 years ago        141MB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;综上所述，JVM 并非容器化应用的好对象。&lt;/p&gt;

&lt;h2 id=&#34;克服-jvm-的限制&#34;&gt;克服 JVM 的限制&lt;/h2&gt;

&lt;p&gt;有两种方式能够克服上述的 JVM 问题&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;使用 Java 9 中引入的模块系统，JDK 提供了一个思路，让原生可执行文件只包含引用到的模块，抛弃其它内容。这样就见效了可执行尺寸。&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&#34;https://www.graalvm.org/&#34; target=&#34;_blank&#34;&gt;Graal VM&lt;/a&gt; 的 Substrate VM&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Substrate VM 是一个能够将 Java 预编译成可执行镜像的框架。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Graal VM 能帮助你：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;把应用打包成单一的 JAR&lt;/li&gt;
&lt;li&gt;从 JAR 创建原生可执行文件&lt;/li&gt;
&lt;li&gt;把原生可执行文件进行容器化&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不幸的是，Jib 没有 GraalVM 的配置。因此需要使用多阶段 Dockerfile：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;构建 JAR&lt;/li&gt;
&lt;li&gt;从 JAR 构建 原生可执行文件&lt;/li&gt;

&lt;li&gt;&lt;p&gt;容器化&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;ARG VERSION=1.10

FROM zenika/alpine-maven:3 as build
COPY src src
COPY pom.xml pom.xml
RUN mvn package

FROM oracle/graalvm-ce:19.2.1 as native
ARG VERSION
COPY --from=build /usr/src/app/target/jvm-operator-$VERSION.jar \
              /var/jvm-operator-$VERSION.jar
WORKDIR /opt/graalvm
RUN gu install native-image \
&amp;amp;&amp;amp; native-image -jar /var/jvm-operator-$VERSION.jar \
&amp;amp;&amp;amp; mv jvm-operator-$VERSION /opt/jvm-operator-$VERSION

FROM scratch
ARG VERSION
WORKDIR /home
COPY --from=native /opt/jvm-operator-$VERSION operator
ENTRYPOINT [&amp;quot;./operator&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Graal VM 发行版中缺省是不包括 Substrate VM 的，因此首先要进行安装&lt;/li&gt;
&lt;li&gt;在前面步骤生成的 JAR 上执行 &lt;code&gt;native-image&lt;/code&gt; 过程&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;scratch&lt;/code&gt; 镜像为基础。在编译过程中使用 &lt;code&gt;--static&lt;/code&gt; 选项打包，来包含所依赖的库&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样就缩减了镜像的尺寸：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;REPOSITORY            TAG          IMAGE ID            CREATED             SIZE
jvm-operator          1.10         340d4d9a767e        6 weeks ago         52.7MB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Substrate VM 包含很多配置项目，为了达到上面的效果，需要这样的一组参数：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;native-image.properties&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;Args=  -J-Xmx3072m \
       --static \
       --allow-incomplete-classpath \
       --no-fallback \
       --no-server \
       -H:EnableURLProtocols=https \
       -H:ConfigurationFileDirectories=/var/config
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;应对反射&#34;&gt;应对反射&lt;/h2&gt;

&lt;p&gt;AOT 过程在反射基础上还有&lt;a href=&#34;https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md&#34; target=&#34;_blank&#34;&gt;诸多限制&lt;/a&gt;。根据底层代码的编写方式不同，可能会受到更多的影响。在不同状况之中，有不同的方法来解决这个问题。这些都将在以后的帖子中介绍：现在我们先来关注一下反射。&lt;/p&gt;

&lt;p&gt;在 Java 中，一些底层代码或多或少依赖于基于运行时的反射。不幸的是，Substrate VM 会删除它认为不需要的代码。不过，这可以通过JSON文件来配置。鉴于依赖反射的调用量，手动配置是一项艰巨的任务。&lt;/p&gt;

&lt;p&gt;Substrate VM 提供了一个更好的选择：它提供了一个 Java 代理，可以在运行中的控制器的命令行中设置。这个代理会拦截控制器应用程序内部的每一个反射调用，并将其记录在一个专门的 &lt;code&gt;reflect-config.json&lt;/code&gt; 文件中。&lt;/p&gt;

&lt;p&gt;在以后的阶段，这个文件（和其他类似的文件一起）可以反馈到编译过程中，这样通过反射访问的代码就会被保留下来。一种方法是通过命令行来送入它们。另一种是将它们打包到 JAR 里面，放在一个专门的文件夹里：这允许库的提供者提供与 AOT 兼容的 JAR，应该是首选的方式。&lt;/p&gt;

&lt;p&gt;根据具体应用的不同，可能还会需要额外的步骤。更多信息，请参考：&lt;a href=&#34;https://blog.frankel.ch/coping-incompatible-code-graalvm-compilation/&#34; target=&#34;_blank&#34;&gt;《How to cope with incompatible code in Graal VM AOT compilation》&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;结论&#34;&gt;结论&lt;/h2&gt;

&lt;p&gt;三篇文章，我们讲述了 Kubernetes 控制器的实现方法。开发过程中我们看到，这并不是一项艰巨的任务。在这其中提到的技术基础之上，能够实现更多更好的功能。&lt;/p&gt;

&lt;p&gt;最后我们在 Kubernetes 集群上运行了新开发的 Java 控制器。后续我们引入 Graal VM 创建了一个原生可执行文件。虽然它使构建过程更加复杂，但使用这样的原生可执行文件消除了 JVM 平台的一些限制：它大大减少了映像大小、内存消耗以及启动时间。&lt;/p&gt;

&lt;p&gt;完整的源码可以在 &lt;a href=&#34;https://github.com/nfrankel/jvm-controller&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt; 上找到&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>自己的 Kubernetes 控制器（2）——用 Java 开发</title>
      <link>/post/your-own-k8s-controller-2/</link>
      <pubDate>Mon, 13 Apr 2020 15:01:52 +0800</pubDate>
      <guid>/post/your-own-k8s-controller-2/</guid>
      <description>

&lt;p&gt;前面文章中，我们大概描述了开发自定义 Kubernetes 控制器的基础内容。其中我们提到，只要能够使用 HTTP/JSON 就可以满足开发需求。本文中就言归正传开始开发。&lt;/p&gt;

&lt;p&gt;开发使用的技术栈可以 Python、NodeJS 或者 Ruby。我的博客叫“Java Geek”，所以这里选择的是 Java。&lt;/p&gt;

&lt;p&gt;这个案例中我们使用 Sidecar 模式：每次有 Pod 调度，就生成一个并行的 Pod；当前面的 Pod 被删除，后面的 Pod 也随之删除。&lt;/p&gt;

&lt;h2 id=&#34;选择合适的工具&#34;&gt;选择合适的工具&lt;/h2&gt;

&lt;p&gt;为了在 Java 中调用 REST 接口，就首先要生成绑定的结构。有几种方式可以完成这项工作：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;最无聊的方式就是手工完成：认真对待所有请求和响应的 JSON 数据，据此开发对应的 Java 对象，选择 JSON 序列化框架，以及 HTTP 客户端。&lt;/li&gt;
&lt;li&gt;次选的方式是使用 Swagger 或者 APiary 这样的代码生成器：API 提供者需要使用某种方式来提供对应的模型，开发者使用相应工具来生成代码。&lt;/li&gt;
&lt;li&gt;最好的方式是，已经有客户端库提供了绑定结构。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kubernetes 属于第三种——它已经为多种语言提供了绑定代码。只不过这种语言封装和 REST API 非常相近，不太符合我的习惯。例如获取所有命名空间下所有 Pod 的代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;ApiClient client = Config.defaultClient();
CoreV1Api core = new CoreV1Api(client);
V1PodList pods =
    core.listPodForAllNamespaces(null, null, null, null, null, null, null, null);
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;所有 &lt;code&gt;null&lt;/code&gt; 都需要传递&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这就是我所说的 &lt;code&gt;和 REST API 非常相近&lt;/code&gt;，幸运的是，还有其他选项：Fabric8 在 &lt;a href=&#34;https://github.com/fabric8io/kubernetes-client&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt; 上提供了 Java API。等价代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;KubernetesClient client = new DefaultKubernetesClient();
PodList pods = client.pods().inAnyNamespace().list();
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;不再需要无用的 &lt;code&gt;null&lt;/code&gt; 参数。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;fabric8-概述&#34;&gt;Fabric8 概述&lt;/h2&gt;

&lt;p&gt;简单说来，Fabric8 API 里面，在 &lt;code&gt;KubernetesClient&lt;/code&gt; 示例中可以获取所有 Kubernetes 资源：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;client.namespaces()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;client.services()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;client.nodes()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;等等&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;根据资源的特性，可以使用命名空间进行过滤：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;client.pods().inAnyNamespace()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;client.pods().inNamespace(&amp;quot;ns&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;列出所有命名空间的所有 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;client.pods().inAnyNamespace().list();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;删除命名空间 &lt;code&gt;ns&lt;/code&gt; 中的所有 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;client.pods().delete(client.pods().inNamespace(&amp;quot;ns&amp;quot;).list().getItems());
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建一个名为 &lt;code&gt;ns&lt;/code&gt; 的命名空间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;client.namespaces()
  .createNew()
    .withApiVersion(&amp;quot;v1&amp;quot;)
    .withNewMetadata()
      .withName(&amp;quot;ns&amp;quot;)
    .endMetadata()
  .done();
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;实现控制回路&#34;&gt;实现控制回路&lt;/h2&gt;

&lt;p&gt;Kubernetes 控制器只是一个控制回路，它会监视集群状态，并尝试将其调整为目标状态。为了跟进调度和删除事件，就需要实现观察者模式。应用订阅事件，在事件发生时，调用相关的回调。&lt;/p&gt;

&lt;p&gt;下面是一个简化版的类图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/watcher-api.svg&#34; alt=&#34;watcher&#34; /&gt;&lt;/p&gt;

&lt;p&gt;实际实现代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class DummyWatcher implements Watcher&amp;lt;Pod&amp;gt; {

  @Override
  public void eventReceived(Action action, Pod pod) {
    switch (action) {
      // 新 Pod
      case ADDED:
        break;
      // Pod 修改
      case MODIFIED:
        break;
      // Pod 删除
      case DELETED:
        break;
      // Pod 出错
      case ERROR:
        break;
    }
  }

  // 删除所有资源。如果客户端正确关闭，`cause` 为 `null`
  @Override
  public void onClose(KubernetesClientException cause) {

  }
}

client.pods()
  .inAnyNamespace()
  .watch(DummyWatcher());
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;细枝末节&#34;&gt;细枝末节&lt;/h2&gt;

&lt;p&gt;我们已经准备好实现 Sidecar 模式了。我不会贴出所有代码，毕竟有 &lt;a href=&#34;https://github.com/nfrankel/jvm-controller&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt;，只会贴出一些必要内容。&lt;/p&gt;

&lt;h3 id=&#34;标记-sidecar&#34;&gt;标记 Sidecar&lt;/h3&gt;

&lt;p&gt;我们的控制器要在 Pod 新建世加入 Sidecar，并在 Pod 移除时也删除 Sidecar。这个逻辑有一点问题：如果 Sidecar pod 被调度，就会触发监控事件，就会加入新的 Sidecar，这个过程会不断重复下去。因此有必要对 Sidecar Pod 进行标记。在带有标记的 Pod 被创建时，不会触发创建逻辑。&lt;/p&gt;

&lt;p&gt;有几种方式来对 Sidecar Pod 进行标记：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;给 Pod 加入后缀，比如 &lt;code&gt;sidecar&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;添加特定标签：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;client.pods()
.inNamespace(&amp;quot;ns&amp;quot;)
.createNew()
.withNewMetadata()
  .addToLabels(&amp;quot;sidecar&amp;quot;, &amp;quot;true&amp;quot;)
.endMetadata()
.done();
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;和-pod-一起删除-sidecar&#34;&gt;和 Pod 一起删除 Sidecar&lt;/h3&gt;

&lt;p&gt;Pod 应该有且只有一个 Sidecar，并且随 Pod 的创建和销毁同步进行创建和销毁。&lt;/p&gt;

&lt;p&gt;因此 Sidecar 数据结构中需要有一个指向主 Pod 的引用。这样在 Pod 删除时，如果它不是 Sidecar Pod，我们就能找到它的 Sidecar 并删除。&lt;/p&gt;

&lt;p&gt;最直白的方式就是在住 Pod 删除时直接删除 Sidecar，不过这需要做不少事。Kubernetes 中可以把两个 Pod 的生命周期使用 &lt;code&gt;ownerReference&lt;/code&gt; 关联起来。这样就可以让 Kubernetes 自行处理删除逻辑了。&lt;/p&gt;

&lt;p&gt;用 API 实现非常直观：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;client.pods()
  .inNamespace(&amp;quot;ns&amp;quot;)
  .createNew()
    .withNewMetadata()
      .addNewOwnerReference()
        .withApiVersion(&amp;quot;v1&amp;quot;)
        .withKind(&amp;quot;Pod&amp;quot;)
        .withName(podName)
        .withUid(pod.getMetadata().getUid())
      .endOwnerReference()
    .endMetadata()
  .done();
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;保持-sidecar&#34;&gt;保持 Sidecar&lt;/h3&gt;

&lt;p&gt;添加了 Sidecar 并不意味着他会永远保持。例如属于一个 Deployment 的 Pod 会被删除，Deployment 的核心功能就是保持副本数为期望值。&lt;/p&gt;

&lt;p&gt;类似的，如果一个 Sidecar 被删除，并且主 Pod 还保持存活，就应该创建新的 Sidecar，并维持 &lt;code&gt;ownerReference&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;结论&#34;&gt;结论&lt;/h2&gt;

&lt;p&gt;本文描述了用 Java 实现 Kubernetes 控制器的过程。有了 Fabric8 API，这个过程相当直接。主要需要解决的问题就是删除和创建逻辑。下一篇也就是最后一篇，会讲解部署和运行的过程。&lt;/p&gt;

&lt;p&gt;本文涉及的完整代码保存在 &lt;a href=&#34;https://github.com/nfrankel/jvm-controller&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;相关链接&#34;&gt;相关链接&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;https://github.com/nfrankel/jvm-controller&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://github.com/fabric8io/kubernetes-client&lt;/code&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>自己的 Kubernetes 控制器（1）——工作准备</title>
      <link>/post/your-own-k8s-controller-1/</link>
      <pubDate>Mon, 13 Apr 2020 11:33:12 +0800</pubDate>
      <guid>/post/your-own-k8s-controller-1/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://blog.frankel.ch/your-own-kubernetes-controller/1/&#34; target=&#34;_blank&#34;&gt;Your own Kubernetes controller - Laying out the work&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://twitter.com/nicolas_frankel&#34; target=&#34;_blank&#34;&gt;Nicolas Fränkel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;时至今日，&lt;a href=&#34;https://kubernetes.io/&#34; target=&#34;_blank&#34;&gt;Kubernetes&lt;/a&gt; 已经成为容器化应用部署的首选平台，是个难以忽视的存在。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kubernetes是一个开源系统，用于自动化部署、扩展和管理容器化应用程序。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;短短几年里，Kubernetes 在 CNCF 的大旗下高歌猛进，在 DevOps 领域已经深入人心。这其中的原因众说纷纭，其中一个非常有说服力的理由是，用户能够避免被锁定在单一云提供商的 API 上。如果你对 2000 年左右微软的桌面垄断有所了解，你可能会明白我的意思。&lt;/p&gt;

&lt;p&gt;Kubernetes 的扩展相对来说比较容易，这是它获得广泛认同的一个重要原因。很多软件供应商在 Docker 镜像之外，还会提供一或多个 Operator。&lt;/p&gt;

&lt;p&gt;我假设读者仅对 Kubernetes 有所了解，对控制器一无所知，在这个假设的基础上，我将用三篇连载来讲述如何使用 Go 以外的语言实现自己的控制器。&lt;/p&gt;

&lt;h2 id=&#34;控制器是什么&#34;&gt;控制器是什么&lt;/h2&gt;

&lt;p&gt;配置管理工具可以分为两种：&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分类&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;th&gt;工具&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;指令式&lt;/td&gt;
&lt;td&gt;指定做事方法，例如启动两个节点&lt;/td&gt;
&lt;td&gt;Ansible、SaltStack 等&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;声明式&lt;/td&gt;
&lt;td&gt;指定目标状态，例如总计五个节点&lt;/td&gt;
&lt;td&gt;Puppet、Chef 等&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;声明式的工具通常会周期性的执行以下任务：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;查询当前状态&lt;/li&gt;
&lt;li&gt;评估要从当前状态达到目标状态所需完成的步骤&lt;/li&gt;
&lt;li&gt;执行这些步骤&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个算法描述的是一个控制回路。&lt;/p&gt;

&lt;p&gt;Kubernetes 里，已经有了这些控制回路的实现。例如 &lt;code&gt;ReplicaSet&lt;/code&gt; 和 &lt;code&gt;Deployment&lt;/code&gt;。这两个对象都可以针对特定镜像设置目标 Pod 数量。Kubernetes 会持续生成副本，直到达到预设的实例数量。如果副本数量发生变化，那么就会新建或删除副本，以达到目标副本数量。&lt;/p&gt;

&lt;p&gt;现在你可能已经猜到了，控制器就是一个控制循环的实现：检查当前状态，用现有状态计算差异，弥补差异。除了 &lt;code&gt;Deployment&lt;/code&gt; 和 &lt;code&gt;ReplicaSet&lt;/code&gt; 的控制器之外，Kubernetes 还提供了很多开箱即用的控制器。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service&lt;/li&gt;
&lt;li&gt;DeamonSet&lt;/li&gt;
&lt;li&gt;PersistentVolume&lt;/li&gt;
&lt;li&gt;Job&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其实大多数的 Kubernetes 资源都是由控制器管理的。&lt;/p&gt;

&lt;h2 id=&#34;初识-operator&#34;&gt;初识 Operator&lt;/h2&gt;

&lt;p&gt;对控制器感兴趣的读者，可能已经在搜索过程中偶然发现了 Operator 这个名词。如果你的时间非常有限，我建议你跳过这一部分，将这两个术语视为近义词即可。&lt;/p&gt;

&lt;p&gt;前面说到 Kubernetes 的扩展性。其中一个扩展方法就是创建控制器，这也是本文的的重点内容。另一个方式就是对 Kubernetes 模型本身进行扩展：在开箱即用的 Pod、Job 等内置资源以外，还可以使用 CRD 来提供额外的资源类型。&lt;/p&gt;

&lt;p&gt;例如下面的代码定义了一个叫做 &lt;code&gt;Hazelcast&lt;/code&gt; 的资源：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;hazelcast-crd.yml&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: hazelcasts.hazelcast.com
spec:
  group: hazelcast.com
  names:
    kind: Hazelcast
    listKind: HazelcastList
    plural: hazelcasts
    singular: hazelcast
  scope: Namespaced
  subresources:
    status: {}
versions:
    - name: v1alpha1
      served: true
      storage: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把文件提交给 API Server，让 Kubernetes 注册这个新的 &lt;code&gt;Hazelcast&lt;/code&gt; CRD。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;kubectl apply -f hazelcast-crd.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个动作完成之后，就可以像其他内置资源一样进行常用操作了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;kubectl get hazelcasts
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;Operator&lt;/code&gt; 就是一个用于某种 CRD 的控制器。如果知道怎么实现控制器，也就能够创建 Operator 了。&lt;/p&gt;

&lt;h2 id=&#34;控制器的需求&#34;&gt;控制器的需求&lt;/h2&gt;

&lt;p&gt;现在我们看看 Kubernetes 控制器的需求。&lt;/p&gt;

&lt;h3 id=&#34;控制器的部署位置&#34;&gt;控制器的部署位置&lt;/h3&gt;

&lt;p&gt;下图是一个简化的 Kubernetes 架构图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/kubernetes-architecture.png&#34; alt=&#34;k8s arch&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 的内置控制器是其控制平面的组成部分。然而自定义控制器是不会出现在这里（Controller Manager）的。控制器没什么限制，它可以在集群内部以 Pod 的形式运行，也可以作为独立的外部进程。&lt;/p&gt;

&lt;p&gt;当然 Pod 形式会享受各种 Kubernetes 上运行容器化应用的福利，例如自愈等。&lt;/p&gt;

&lt;h3 id=&#34;和-kubernetes-的通信&#34;&gt;和 Kubernetes 的通信&lt;/h3&gt;

&lt;p&gt;在 Kubernetes 中，API Server 是一个通信组件。客户端发送 HTTP 请求，API Server 处理请求后发回响应。给 &lt;code&gt;kubectl&lt;/code&gt; 加上参数就能观察到这一过程：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods --v=8
I0209 12:36:31.330067   13717 round_trippers.go:420] GET https://192.168.99.103:8443/api/v1/namespaces/default/pods?limit=500
I0209 12:36:31.330078   13717 round_trippers.go:427] Request Headers:
I0209 12:36:31.330081   13717 round_trippers.go:431]     Accept: application/json;as=Table;v=v1beta1;g=meta.k8s.io, application/json
I0209 12:36:31.330085   13717 round_trippers.go:431]     User-Agent: kubectl/v1.17.2 (darwin/amd64) kubernetes/59603c6
I0209 12:36:31.339770   13717 round_trippers.go:446] Response Status: 200 OK in 9 milliseconds
I0209 12:36:31.339780   13717 round_trippers.go:449] Response Headers:
I0209 12:36:31.339798   13717 round_trippers.go:452]     Content-Length: 2933
I0209 12:36:31.339804   13717 round_trippers.go:452]     Date: Sun, 09 Feb 2020 11:36:31 GMT
I0209 12:36:31.339822   13717 round_trippers.go:452]     Content-Type: application/json
I0209 12:36:31.340084   13717 request.go:1017] Response Body:
{ &amp;quot;kind&amp;quot;:&amp;quot;Table&amp;quot;,
  &amp;quot;apiVersion&amp;quot;:&amp;quot;meta.k8s.io/v1beta1&amp;quot;,
  &amp;quot;metadata&amp;quot;:{
    &amp;quot;selfLink&amp;quot;:&amp;quot;/api/v1/namespaces/default/pods&amp;quot;,
    &amp;quot;resourceVersion&amp;quot;:&amp;quot;2387836&amp;quot; },
  &amp;quot;columnDefinitions&amp;quot;:[
    { &amp;quot;name&amp;quot;:&amp;quot;Name&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;string&amp;quot;,
      &amp;quot;format&amp;quot;:&amp;quot;name&amp;quot;,
      &amp;quot;description&amp;quot;:&amp;quot;Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names&amp;quot;,
      &amp;quot;priority&amp;quot;:0 },
    { &amp;quot;name&amp;quot;:&amp;quot;Ready&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;string&amp;quot;,
      &amp;quot;format&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;description&amp;quot;:&amp;quot;The aggregate readiness state of this pod for accepting traffic.&amp;quot;,
      &amp;quot;priority&amp;quot;:0 },
    { &amp;quot;name&amp;quot;:&amp;quot;Status&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;string&amp;quot;,
      &amp;quot;format&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;description&amp;quot;:&amp;quot;The aggregate status of the containers in this pod.&amp;quot;,
      &amp;quot;priority&amp;quot;:0 },
    { &amp;quot;name&amp;quot;:&amp;quot;Restarts&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;integer&amp;quot;,
      &amp;quot;format&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;description&amp;quot;:&amp;quot;The number of times the containers in this pod have been restarted.&amp;quot;,
      &amp;quot;priority&amp;quot;:0 },
    { &amp;quot;name&amp;quot;:&amp;quot;Age&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;stri
[truncated 1909 chars]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个通信过程的需求很简单：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;能够处理 HTTP 的请求和响应&lt;/li&gt;
&lt;li&gt;JSON 解析（或者说序列化和反序列化）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;是的，有 JSON 和 HTTP 的处理能力就够了，所以要编写一个控制器，并不一定必须使用特定语言（例如 Go），理论上用单纯的 Shell 也是可以实现的。&lt;/p&gt;

&lt;h2 id=&#34;go-的定位&#34;&gt;Go 的定位&lt;/h2&gt;

&lt;p&gt;在进入实现细节之前，首先要看看 Kubernetes 的生态。&lt;/p&gt;

&lt;p&gt;历史上好像 Kubernetes 的祖先是用 Java 开发的，后来被移植到了 Go 上。这可能是部分代码不符合 Go 语言风格的原因。尽管 Go 具有垃圾收集功能，但它还是被称为一种低级语言，很适合运行接近于裸机的软件。这种说法是否成立，远远超出了本文的范围，也超出了我的能力。&lt;/p&gt;

&lt;p&gt;然而 Kubernetes 生态中大量软件是使用 Go 语言编写的，我想是有其原因的。&lt;/p&gt;

&lt;p&gt;如果你已经对 Go 相当了解，那么继续使用是个很好的选择——改弦易辙需要勇气。这并不只是一个语言的问题，除了语法之外，还有很多其他内容：&lt;/p&gt;

&lt;h3 id=&#34;要多久才能用新语言写出地道的代码&#34;&gt;要多久才能用新语言写出地道的代码&lt;/h3&gt;

&lt;p&gt;我记得我在学习 Java 的时候，读过 C 语言开发者写的代码。虽然语法是 Java，但是却写出了 C 语言的风格，例如在方法结束之前释放本地变量的引用。&lt;/p&gt;

&lt;h3 id=&#34;多久才能搞清楚在什么条件下使用什么库&#34;&gt;多久才能搞清楚在什么条件下使用什么库&lt;/h3&gt;

&lt;p&gt;我不了解 Go，但是我知道 Java。Java 生态的丰富是人所皆知的。例如测试的场景，就有 JUnit 4、JUnit 5 以及 TestNG 可以选择，另外需要加入断言库么？这还只是测试呢。&lt;/p&gt;

&lt;h3 id=&#34;选择正确的工具链要多久&#34;&gt;选择正确的工具链要多久&lt;/h3&gt;

&lt;p&gt;如果已经在使用 JetBrains 的产品，那么从 JetBrains IDE 之间跳转是比较容易的，例如 IDEA 和 GoLand。但是 IDE 市场非常混乱，例如微软正在推广的包含丰富插件的 VS Code。而 Java 世界中，Eclipse 仍然占据客观的市场份额。各种产品都有自己的优劣，自己的拥趸。工具的选择可能在组织内部引发圣战。&lt;/p&gt;

&lt;h3 id=&#34;新工具形成生产力要多久&#34;&gt;新工具形成生产力要多久&lt;/h3&gt;

&lt;p&gt;各种 IDE 都有各自的玩法。例如我从 Eclipse 切换到 IntelliJ 的过程中，几个星期后才停掉了频繁保存文件的习惯。除了 IDE 之外，还有除错工具等。新的语言能怎么除错？有什么先决条件么？&lt;/p&gt;

&lt;p&gt;另外前面说的几个点只是开发，如果考虑到相关的构建、集成和投产环境，其投入可能又会有数倍的增长。&lt;/p&gt;

&lt;p&gt;我希望上面几点能够让读者意识到，语言的切换事关重大。在很多情况下，沿用原有的语言可能是个更好的选择。&lt;/p&gt;

&lt;h2 id=&#34;结论&#34;&gt;结论&lt;/h2&gt;

&lt;p&gt;本文的第一部分，大概了解了一下 Kubernetes 控制器的基础内容。我们详细介绍了什么是控制器，以及开发控制器的需要：即能够与 HTTP/JSON 通信。在下一篇帖子中，我们将详细介绍并实际开发自己的自定义控制器。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>马后炮：Operator for Spark 之后</title>
      <link>/post/operator-between-dev-and-ops/</link>
      <pubDate>Fri, 01 Feb 2019 21:12:34 +0800</pubDate>
      <guid>/post/operator-between-dev-and-ops/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.fleeto.us/post/google-announces-k8s-operator-for-spark/&#34; target=&#34;_blank&#34;&gt;Google 宣布 Kubernetes Operator for Spark&lt;/a&gt; 之后，朋友们的评价主要集中在 GCP 对大数据的浓厚兴趣上；我觉得还有一个解读就是，我以前可能低估了 Operator 的重要地位，因此有了本文。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;

&lt;p&gt;CoreOS 最初在 2016 年底发布 Operator 概念时，称其主旨为：&lt;a href=&#34;https://coreos.com/blog/introducing-operators.html&#34; target=&#34;_blank&#34;&gt;Putting Operational Knowledge into Software&lt;/a&gt;，也就是将运维技能融入软件，在翻译该声明时，也只是觉得这一说法很有趣，但是在 GCP 发布了 Spark Operator 之后，我觉得似乎有必要回顾一下，Operator 到底是要用来做什么的。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;我们的团队正在 Kubernetes 社区进行一个概念的设计和实现，这一概念就是：在 Kubernetes 基础之上，可靠的创建、配置和管理复杂应用的方法。
我们把这种软件称为 Operator。一个 Operator 指的是一个面向特定应用的控制器，这一控制器对 Kubernetes API 进行了扩展，使用 Kubernetes 用户的行为方式，创建、配置和管理复杂的有状态应用的实例。他构建在基础的 Kubernetes 资源和控制器概念的基础上，但是包含了具体应用领域的运维知识，实现了日常任务的自动化。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;上面一篇文字来自我对原文的译稿：&lt;a href=&#34;https://blog.fleeto.us/post/operator-for-kubernetes/&#34; target=&#34;_blank&#34;&gt;Operator：固化到软件中的运维技能&lt;/a&gt;，这一段文字分析一下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;复杂应用：一般来说，Kubernetes 上的复杂应用，指的主要是两种：有状态或者有协作。&lt;/li&gt;
&lt;li&gt;创建、配置和管理：在软件的开发阶段之后，就进入了 Operator 的管理范围了。&lt;/li&gt;
&lt;li&gt;使用 Kubernetes 用户的行为方式：Kubectl、API Server、声明式的 API、资源、YAML。。。&lt;/li&gt;
&lt;li&gt;特定应用的控制器：在软件的“本体”之外，还需要实现一个控制单元，用来完成对专属资源的解释和执行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;粗看上来，这东西有点多余，多学东西、多写东西，为的就是在 Kubernetes 上用 Kubectl 操作 YAML 的方式来装软件配置软件？老夫早已熟悉各种 ini xml json toml 等乌七八糟的配置方式。要这东西有什么用呢？&lt;/p&gt;

&lt;p&gt;为了示范 Operator 的功能，CoreOS 特意开放了两个 Operator：&lt;a href=&#34;https://github.com/coreos/etcd-operator&#34; target=&#34;_blank&#34;&gt;ETCD&lt;/a&gt; 和 &lt;a href=&#34;https://github.com/coreos/prometheus-operator&#34; target=&#34;_blank&#34;&gt;Prometheus&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;例如 ETCD Operator，通过一定的 YAML 定义，可以完成以下功能：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;创建集群&lt;/li&gt;
&lt;li&gt;集群伸缩&lt;/li&gt;
&lt;li&gt;故障应对&lt;/li&gt;
&lt;li&gt;集群升级&lt;/li&gt;
&lt;li&gt;备份和恢复&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;同时 CoreOS 还提供了 &lt;a href=&#34;https://github.com/operator-framework&#34; target=&#34;_blank&#34;&gt;Operator Framework&lt;/a&gt; 进行 Operator 的开发。&lt;/p&gt;

&lt;h2 id=&#34;当运维遇上-operator&#34;&gt;当运维遇上 Operator&lt;/h2&gt;

&lt;p&gt;Operator 的功能，稍微理解一下，就看得出它提出了一个新的运维方式：使用 Kubernetes 原生 API 或者 Kubectl 等基于这种 API 的工具来替换原本各走各路的运维方式。把原有的各种系统的部署和配置方式，转换为 Kubernetes 世界中的 CRD，利用 CRD 的资源对象来完成各种运维任务。&lt;/p&gt;

&lt;p&gt;这实际上是将运维操作进行了一次抽象，用一致的界面来完成各种不同的运维动作，在理想情况下，一个软件系统提供的 Operator 丰富到覆盖其所有应用场景，那么他的所有运维操作都是可以通过 API Server 接口来进行控制，这给运维工作带来很大的便利：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;避免学习该软件的配置方言，降低上手难度。&lt;/li&gt;
&lt;li&gt;使用模板渲染的方式，能够迅速的将该软件的运维需要转换为流程化、自动化操作。&lt;/li&gt;
&lt;li&gt;增强的控制能力，能够方便的进行 CI/CD 集成。&lt;/li&gt;
&lt;li&gt;声明式 API，对运维过程的管理，提供了更高的透明、可控、版本化等优势。&lt;/li&gt;
&lt;li&gt;现有的控制门户等运维设施，也能完成新系统的快速接入。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上述种种优势，对运维工作来说都是很有帮助的，然而面对现实，还是有一些限制的：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;平台限制：很明显，几乎只有 Kubernetes 上的应用才能享受 Operator 的优势。&lt;/li&gt;
&lt;li&gt;数量稀少：目前有提供 Operator 的软件还是九牛一毛，未能产生规模效应，配置方言的学习并无法避免。&lt;/li&gt;
&lt;li&gt;设计困难：Operator Controller 定义 CRD，然后使用 API 提交 CRD 资源对象的方式看上去很不错，但是要用 CRD 定义一个系统的所有场景，谈何容易，官方的 Prometheus Operator 基本无法生产应用就是明证。&lt;/li&gt;
&lt;li&gt;Operator 失败的情况下，其原始的方言配置未必能够在 Kubernetes + Operator 环境中奏效。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最大的一个问题，Operator 是需要开发的，谁来负责他的工作量呢？&lt;/p&gt;

&lt;h2 id=&#34;当开发遇上-operator&#34;&gt;当开发遇上 Operator&lt;/h2&gt;

&lt;p&gt;对开发来说，Operator 如果流行，可能不是一个好消息。&lt;/p&gt;

&lt;p&gt;目前的企业系统开发中，通常对于最终用户的界面、以及应用之间的 API 管理都有详尽的需求、设计和测试等文档要求，然而在&lt;strong&gt;运维是自己人&lt;/strong&gt;的情况下，因为&lt;strong&gt;并不影响交付&lt;/strong&gt;，很多&lt;strong&gt;中间、自用系统&lt;/strong&gt;的运维工作设计，往往就糊弄了事了&lt;/p&gt;

&lt;p&gt;如果 Operator 落地开花，就产生了一个副作用——需要修改交付标准：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;把运维当成用户&lt;/li&gt;
&lt;li&gt;运维用户自然也应该有针对性的需求调研分析&lt;/li&gt;
&lt;li&gt;要有详尽的运维场景设计&lt;/li&gt;
&lt;li&gt;对于 Operator CRD 无法完整覆盖的情况下，要有降级的措施准备。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;

&lt;p&gt;通过上面粗浅的分析，可以看出要落地使用 Operator，对 DevOps 的两端都需要有一点点的变化：Dev 的交付标准，和 Ops 的工作方式。&lt;/p&gt;

&lt;p&gt;带来的好处也不太醒目：声明化、标准化和自动化。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>换马甲：十分钟 Helm 变 Operator</title>
      <link>/post/helm-to-operator/</link>
      <pubDate>Thu, 30 Aug 2018 10:41:42 +0800</pubDate>
      <guid>/post/helm-to-operator/</guid>
      <description>

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

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

&lt;blockquote&gt;
&lt;p&gt;目前版本相当幼稚，看看就好了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;准备工作&#34;&gt;准备工作&lt;/h2&gt;

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

&lt;p&gt;使用 git 获取 &lt;a href=&#34;https://github.com/operator-framework/helm-app-operator-kit&#34; target=&#34;_blank&#34;&gt;Helm app operator kit&lt;/a&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ git clone https://github.com/operator-framework/helm-app-operator-kit.git
Cloning into &#39;helm-app-operator-kit&#39;...
...
Resolving deltas: 100% (58/58), done.
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;镜像构建&#34;&gt;镜像构建&lt;/h2&gt;

&lt;p&gt;下载一个实验 Chart &lt;strong&gt;并解压&lt;/strong&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ 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
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里的 Dockerfile 可以略微关注一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;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 [&amp;quot;/operator&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;使用 Dockerfile 进行构建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;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 .
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HELM_CHART&lt;/strong&gt;：我们之前解压的 Chart 目录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API_VERSION&lt;/strong&gt;：即将用到的自定义资源的 API 组和版本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KIND&lt;/strong&gt;：自定义资源名称。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker 构建完成之后，将新镜像 Push 到 Kubernetes 可访问的镜像库中。&lt;/p&gt;

&lt;h2 id=&#34;operator-部署&#34;&gt;Operator 部署&lt;/h2&gt;

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

&lt;h3 id=&#34;rbac-yaml&#34;&gt;rbac.yaml&lt;/h3&gt;

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

&lt;blockquote&gt;
&lt;p&gt;这里的 RoleBinding 只是绑定到了 default 命名空间的 default ServiceAccount，如果要给 Operator Pod 单独赋权，就要对 &lt;code&gt;subject&lt;/code&gt; 进行修改。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;文件编辑结束后，就可以使用 &lt;code&gt;kubectl apply&lt;/code&gt; 提交到集群运行。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: helm-app-operator
rules:
...
  - secrets
  - namespaces
  verbs:
  - &amp;quot;*&amp;quot;
- apiGroups:
  - apps
  resources:
  - deployments
  - daemonsets
  - replicasets
  - statefulsets
  verbs:
  - &amp;quot;*&amp;quot;
...
- apiGroups:
  - anywhere.io
  resources:
  - &amp;quot;*&amp;quot;
  verbs:
  - &amp;quot;*&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;crd-yaml&#34;&gt;crd.yaml&lt;/h3&gt;

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

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;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 # 版本
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;同样的，使用 &lt;code&gt;kubectl apply&lt;/code&gt; 提交这一定义给 Kubernetes 集群。&lt;/p&gt;

&lt;h3 id=&#34;operator-yaml&#34;&gt;operator.yaml&lt;/h3&gt;

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

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

&lt;h2 id=&#34;创建应用实例&#34;&gt;创建应用实例&lt;/h2&gt;

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

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: anywhere.io/v1alpha1
kind: memcached
metadata:
  name: memcached-yy
  labels:
    app: example-app
spec:
  replicaCount: 3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl apply&lt;/code&gt; 提交之后，可以看到集群上开始创建这一实例：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ 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
...
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;这里 Operator Pod 可能会崩溃，删除即可正常工作😄。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;查询实例情况&#34;&gt;查询实例情况：&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get memcached
NAME           CREATED AT
memcached-r    10h
memcached-yy   10h
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;删除实例&#34;&gt;删除实例&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl delete memcached memcached-yy
memcached.anywhere.io &amp;quot;memcached-yy&amp;quot; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次使用 &lt;code&gt;kubectl get po&lt;/code&gt;，会发现对应 Pod 已经删除。&lt;/p&gt;

&lt;h2 id=&#34;结语&#34;&gt;结语&lt;/h2&gt;

&lt;p&gt;CoreOS 再次提供了一个有趣的方向，有效的降低了 Operator 的入门门槛。但是这一方案除了成熟度相当不足之外，Helm 本身对运维的支持其实也是非常弱的，对有状态应用是无论如何不能使用这种方式来进行运维的。——马甲，只是马甲。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Operator：固化到软件中的运维技能</title>
      <link>/post/operator-for-kubernetes/</link>
      <pubDate>Wed, 13 Dec 2017 18:24:52 +0800</pubDate>
      <guid>/post/operator-for-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://coreos.com/blog/introducing-operators.html&#34; target=&#34;_blank&#34;&gt;Introducing Operators: Putting Operational Knowledge into Software&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SRE 是用开发软件的方式来进行应用运维的人。他们是工程师、开发者，通晓如何进行软件开发，尤其是特定应用域的开发。他们做出的东西，就是包含这一应用的运维领域技能的软件。&lt;/p&gt;

&lt;p&gt;我们的团队正在 Kubernetes 社区进行一个概念的设计和实现，这一概念就是：在 Kubernetes 基础之上，可靠的创建、配置和管理复杂应用的方法。&lt;/p&gt;

&lt;p&gt;我们把这种软件称为 Operator。一个 Operator 指的是一个面向特定应用的控制器，这一控制器对 Kubernetes API 进行了扩展，使用 Kubernetes 用户的行为方式，创建、配置和管理复杂的有状态应用的实例。他构建在基础的 Kubernetes 资源和控制器概念的基础上，但是包含了具体应用领域的运维知识，实现了日常任务的自动化。&lt;/p&gt;

&lt;h2 id=&#34;无状态容易-有状态难&#34;&gt;无状态容易，有状态难&lt;/h2&gt;

&lt;p&gt;在 Kubernetes 的支持下，管理和伸缩 Web 应用、移动应用后端以及 API 服务都变得比较简单了。其原因是这些应用一般都是无状态的，所以 Deployment 这样的基础 Kubernetes API 对象就可以在无需附加操作的情况下，对应用进行伸缩和故障恢复了。&lt;/p&gt;

&lt;p&gt;而对于数据库、缓存或者监控系统等有状态应用的管理，就是个挑战了。这些系统需要应用领域的知识，来正确的进行伸缩和升级，当数据丢失或不可用的时候，要进行有效的重新配置。我们希望这些应用相关的运维技能可以编码到软件之中，从而借助 Kubernetes 的能力，正确的运行和管理复杂应用。&lt;/p&gt;

&lt;p&gt;Operator 这种软件，使用 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/thirdpartyresources/&#34; target=&#34;_blank&#34;&gt;TPR(第三方资源，现在已经升级为 CRD)&lt;/a&gt; 机制对 Kubernetes API 进行扩展，将特定应用的知识融入其中，让用户可以创建、配置和管理应用。和 Kubernetes 的内置资源一样，Operator 操作的不是一个单实例应用，而是集群范围内的多实例。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/etcd_operator.png&#34; alt=&#34;etcd operator&#34; /&gt;&lt;/p&gt;

&lt;p&gt;为了展示 Operator 的概念，我们有两个实际的例子开放了源代码：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://coreos.com/blog/introducing-the-etcd-operator.html&#34; target=&#34;_blank&#34;&gt;etcd Operator&lt;/a&gt;，创建、配置和管理 etcd 集群。etcd 是一个可靠的分布式键值库，由 CoreOS 出品，用于分布式系统中的关键数据存储，Kubernetes 就是用户之一。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://coreos.com/blog/the-prometheus-operator.html&#34; target=&#34;_blank&#34;&gt;Prometheus Operator&lt;/a&gt;，创建配置和管理 Prometheus 监控实例。Prometheus 是一个强大的监控、指标和告警工具，也是 CoreOS 团队支持的 CNCF 项目。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;operator-如何构建&#34;&gt;Operator 如何构建？&lt;/h2&gt;

&lt;p&gt;Operator 基于两个 Kubernetes 的核心概念：资源和控制器。例如内置的 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/replicasets/&#34; target=&#34;_blank&#34;&gt;ReplicaSet&lt;/a&gt; 资源让用户能够设置指定数量的 Pod 来运行，Kubernetes 内置的控制器会通过创建或移除 Pod 的方式，来确保 ReplicaSet 资源的状态合乎期望。Kubernetes 中有很多基础的控制器和资源用这种方式进行工作，包括 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/services/&#34; target=&#34;_blank&#34;&gt;Service&lt;/a&gt;，&lt;a href=&#34;http://kubernetes.io/docs/user-guide/deployments/&#34; target=&#34;_blank&#34;&gt;Deployment&lt;/a&gt; 以及 &lt;a href=&#34;http://kubernetes.io/docs/admin/daemons/&#34; target=&#34;_blank&#34;&gt;DaemonSet&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/rs_before.png&#34; alt=&#34;RS Before&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;用户将一个 Pod 的 RS 扩展到 三个&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&#34;images/rs_scaled.png&#34; alt=&#34;RS Scaled&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;一段时间之后，Kubernetes 的控制器按照用户意愿创建新的 Pod。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Operator 在基础的 Kubernetes 资源和控制器之上，加入了相关的知识和配置，让 Operator 能够执行特定软件的常用任务。例如当手动对 etcd 集群进行伸缩的时候，用户必须执行几个步骤：为新的 etcd 示例创建 DNS 名称，加载新的 etcd 示例，使用 etcd 管理工具(&lt;code&gt;etcdctl member add&lt;/code&gt;)来告知现有集群加入新成员。&lt;strong&gt;etcd Operator&lt;/strong&gt; 的用户就只需要简单的把 etcd 的集群规模字段加一而已。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/operator_scale.png&#34; alt=&#34;Operator Scale&#34; /&gt;
&amp;gt; 用户使用 kubectl 触发了一次备份&lt;/p&gt;

&lt;p&gt;复杂的管理任务还有很多，包括应用升级、配置备份、原生 Kubernetes API 的服务发现，应用的 TLS 认证配置以及灾难恢复等。&lt;/p&gt;

&lt;h2 id=&#34;如何创建一个-operator&#34;&gt;如何创建一个 Operator&lt;/h2&gt;

&lt;p&gt;根据前面的陈述，我们知道 Operator 是跟应用紧密相关的，所以其中最重要的工作就是把应用自身的运维方法编码成为资源和控制逻辑。在创建 Operator 的过程中，我们发现了一些适用于各种应用的通用模式：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Operator 应该以单一 Deployment 的形式进行安装。&lt;code&gt;kubectl create -f https://coreos.com/operators/etcd/latest/deployment.yaml&lt;/code&gt;，不应进行其他额外操作。&lt;/li&gt;
&lt;li&gt;Operator 在安装到 Kubernetes 中时，应该创建新的 TPR 类型。用户会使用这一类型来创建新的应用实例。&lt;/li&gt;
&lt;li&gt;Operator 应该尽量利用 Kubernetes 内置的 Service 以及 ReplicaSet 这些经过良好的测试并易于理解的原生对象。&lt;/li&gt;
&lt;li&gt;Operator 应该向后兼容，并且保持对过去版本资源的理解能力。&lt;/li&gt;
&lt;li&gt;在 Operator 出现故障或者被移除的时候，相关应用应该持续运行不受影响。&lt;/li&gt;
&lt;li&gt;用户应该从 Operator 中获得声明特定版本以及编排应用版本升级的能力。无法更新的软件应该是一种运维缺陷，也可能造成安全问题，Operator 应该给用户更多信心和辅助，完成升级操作。&lt;/li&gt;
&lt;li&gt;Operator 应该用“Chaos Monkey”这样的测试套件来模拟 Pod、配置或者网络故障的情况下的运行情况。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;operators-的未来&#34;&gt;Operators 的未来&lt;/h2&gt;

&lt;p&gt;CoreOS 所发布的 etcd 和 Prometheus 的 Operator，展示了 Kubernetes 平台的能力。过去一年中，我们和 Kubernetes 社区紧密合作，聚焦于 Kubernetes 的稳定性、安全性、以及管理和安装的方便性方面的工作。&lt;/p&gt;

&lt;p&gt;现在 Kubernetes 的基础已经奠定，我们新的工作重点转移到了上层建筑：用软件来对 Kubernetes 进行扩展，为其赋予新的能力。我们想象，未来用户在各自的 Kubernetes 集群上安装 Postgress Operator、Cassandra Operator 或者 Redis Operator，像对普通 Web 应用一样对这些应用进行伸缩。&lt;/p&gt;

&lt;p&gt;要了解更多内容，可以浏览 Github 仓库，在我们的&lt;a href=&#34;https://coreos.com/community/&#34; target=&#34;_blank&#34;&gt;社区中&lt;/a&gt;讨论。&lt;/p&gt;

&lt;h2 id=&#34;faq&#34;&gt;FAQ&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Q：和 StatefulSet（从前的 PetSet）有什么区别？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A：有的应用需要集群提供“有状态资源”，例如静态 IP 或者存储，StatefulSet 让 Kubernetes 有了支持这种应用的能力。然而有的应用需要更多的有状态部署模型的支持，例如故障的告警和应对、备份、重新配置等。所以 Operator 应用可以根据部署特性需求来选择 StatefulSets 或者 ReplicaSets 以及 Deployments。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Q：和 Chef、Puppet 这样的配置管理系统相比呢？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A：容器和 Kubernetes 的给了 Operator 生存基础。这两个技术让新软件的部署、分布式配置的协调、检查多主机系统状态等工作变得轻而易举。Operator 把这种种优势聚合在一起，为应用的用户提供方便；他提供的不仅仅是配置，还包括上线、状态等全部内容。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Q：和 Helm 的区别？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A：Helm 是一个把多个 Kubernetes 资源包装为一个单独软件包的工具。把多个应用集成在一起的概念，可以和 Operator 的活动管理进行互补。例如 Traefik 是一个负载均衡，他可以使用 ETCD 作为后端数据库。可以创建一个 Helm Chart ，同时部署 Traefik 的 Deployment 对象以及 etcd 集群。也可以使用 etcd Operator 进行 etcd 集群的部署和管理。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Q：这对于 Kubernetes 的新用户来说意味着什么？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A：这对新用户没什么影响，而且可以更简便的部署 etcd、Prometheus 这样的复杂应用，并且以后会有更多软件支持。我们推荐的试水方式是 &lt;a href=&#34;https://github.com/kubernetes/minikube&#34; target=&#34;_blank&#34;&gt;minikube&lt;/a&gt; 以及 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/kubectl/kubectl_run/&#34; target=&#34;_blank&#34;&gt;kubectl run&lt;/a&gt;，然后可以用 &lt;code&gt;kubectl run&lt;/code&gt;启动 Prometheus Operator 来监控部署的应用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Q：etcd 和 Prometheus Operator 的代码开放了么？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A：是的，分别位于 &lt;a href=&#34;https://github.com/coreos/etcd-operator&#34; target=&#34;_blank&#34;&gt;https://github.com/coreos/etcd-operator&lt;/a&gt; 以及 &lt;a href=&#34;https://github.com/coreos/prometheus-operator&#34; target=&#34;_blank&#34;&gt;https://github.com/coreos/prometheus-operator&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Q：是否有计划开发其他的 Operator？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A：未来会的。我们还希望社区能够更多参与，让我们也知道用户需要什么样的 Operator。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Q：Operator 能够让集群更安全么？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A：无法更新的软件是一个常见的问题原因和安全隐患，Operator 能让用户更自信的进行升级，打破这一限制。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Q：Operator 能够帮助进行灾难恢复么？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A：Operator 能够轻松地对应用进行阶段性备份以及恢复。我们还希望开发一个功能，让用户可以从备份开始安装新的实例。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>
