<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>kubernetes | 伪架构师</title>
    <link>/tags/kubernetes/</link>
      <atom:link href="/tags/kubernetes/index.xml" rel="self" type="application/rss+xml" />
    <description>kubernetes</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Wed, 21 Feb 2024 23:37:11 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>kubernetes</title>
      <link>/tags/kubernetes/</link>
    </image>
    
    <item>
      <title>不用 API Server 也能运行 Pod？</title>
      <link>/post/kubelet-stand-alone/</link>
      <pubDate>Wed, 21 Feb 2024 23:37:11 +0800</pubDate>
      <guid>/post/kubelet-stand-alone/</guid>
      <description>

&lt;p&gt;遇到一个奇怪的需求：想复用 Pod 的 YAML，但是家境贫寒，不想搞个高可用 API Server；又惜字如金，不想上 Docker Compose。一顿 Google 猛如虎之后，得到了两个方案：静态 Pod 和 &lt;code&gt;podman play kube&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;静态-pod&#34;&gt;静态 Pod&lt;/h2&gt;

&lt;p&gt;Kubernetes 有个功能，就是 &lt;code&gt;static pod&lt;/code&gt;，官网介绍大致如下：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;静态 Pod 由特定节点上的 kubelet 守护进程直接管理的，API 服务器并不关注静态 Pod。通常说来，Pod 是由 Deployments 之类的控制器管理的，而静态 Pod 则是在 Kubelet 的看护之下，并负责其重新启动的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;那么 Kubelet 是否可以脱离 API Server 直接运行呢？答案是肯定的，Kelsey Hightower 早在七年前就做了这样的尝试。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/kelseyhightower/standalone-kubelet-tutorial&#34; target=&#34;_blank&#34;&gt;https://github.com/kelseyhightower/standalone-kubelet-tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;想法很简单，单独运行一个 Kubelet，使用 Kubelet 拉起磁盘上的 Pod 文件。&lt;/p&gt;

&lt;h3 id=&#34;测试&#34;&gt;测试&lt;/h3&gt;

&lt;p&gt;以目前最新版本的 &lt;code&gt;1.29&lt;/code&gt; 为例，在 Ubuntu 中按照默认方式使用 &lt;code&gt;apt&lt;/code&gt; 部署 Containerd：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ apt install containerd cri-tools
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后按照官网文档安装 kubelet：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ apt-get install -y apt-transport-https ca-certificates curl gpg
$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
$ echo &#39;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /&#39; | sudo tee /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update
$ sudo apt-get install -y kubelet
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;编写如下 &lt;code&gt;kubelet.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
enableServer: false
staticPodPath: /home/kubelet/pods
readOnlyPort: 10250
failSwapOn: false
podCIDR: 10.241.1.0/24
authentication:
  anonymous:
    enabled: true
  webhook:
    enabled: false
authorization:
  mode: AlwaysAllow
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后，我们启动 Kubelet：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubelet --config=kubelet.yaml
I0302 11:39:14.006446    9890 server.go:487] &amp;quot;Kubelet version&amp;quot; kubeletVersion=&amp;quot;v1.29.2&amp;quot;
I0302 11:39:14.006492    9890 server.go:489] &amp;quot;Golang settings&amp;quot; GOGC=&amp;quot;&amp;quot; GOMAXPROCS=&amp;quot;&amp;quot; GOTRACEBACK=&amp;quot;&amp;quot;
I0302 11:39:14.006622    9890 server.go:650] &amp;quot;Standalone mode, no API client&amp;quot;
I0302 11:39:14.010584    9890 server.go:538] &amp;quot;No api server defined - no events will be sent to API server&amp;quot;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;注意，如果使用其它配置方法的容器运行时，可能需要指定不同的 &lt;code&gt;Endpoint&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这里会看到，日志中直接就表明这是一个独立运行模式的 Kubelet。&lt;/p&gt;

&lt;p&gt;最后只要把一个 Pod 定义的文件拷贝到上文配置中的指定目录就能启动 Pod 了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: apache
spec:
  containers:
  - name: apache
    image: httpd
    ports:
    - name: http
      containerPort: 80
      hostPort: 45678
    volumeMounts:
    - name: local
      mountPath: /data
  volumes:
  - name: local
    hostPath:
      path: /home/volumes/data
      type: Directory

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 &lt;code&gt;crictl&lt;/code&gt; 查看运行中的 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$  sudo crictl ps
55a65b4642f47       50a1bd9b297f7       18 seconds ago      Running             apache              0                   c141f4e021cdf       apache-ubuntu
$ curl http://127.0.0.1:45678
&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;It works!&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 已经启动。&lt;/p&gt;

&lt;h3 id=&#34;限制&#34;&gt;限制&lt;/h3&gt;

&lt;p&gt;因为没有 API Server 的支持，所以静态 Pod 里面是无法引用 &lt;code&gt;Configmap&lt;/code&gt;、&lt;code&gt;Secret&lt;/code&gt; 之类的外部对象的。更不要提 &lt;code&gt;Deployment&lt;/code&gt; 了。&lt;/p&gt;

&lt;h2 id=&#34;podman-play-kube&#34;&gt;Podman Play Kube&lt;/h2&gt;

&lt;p&gt;和独立模式的 Kubelet 不同，&lt;code&gt;podman play kube&lt;/code&gt; 支持的 Kubernetes 对象除了 Pod 之外，还支持：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;li&gt;PVC&lt;/li&gt;
&lt;li&gt;Configmap&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;启动-pod&#34;&gt;启动 Pod&lt;/h3&gt;

&lt;p&gt;Ubuntu 下可以直接使用 &lt;code&gt;apt install podman&lt;/code&gt; 安装部署。安装结束后，可以复用刚才的 &lt;code&gt;pod.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ podman play kube pod.yaml
a container exists with the same name (&amp;quot;apache&amp;quot;) as the pod in your YAML file; changing pod name to apache_pod
Pod:
...
Container:
...

$ podman pod ls
POD ID        NAME        STATUS      CREATED        INFRA ID      # OF CONTAINERS
99e235dfe7a3  apache_pod  Running     9 seconds ago  b54991e35f58  2

$ podman ps
CONTAINER ID  IMAGE                           COMMAND           CREATED         STATUS             PORTS                  NAMES
b54991e35f58  k8s.gcr.io/pause:3.5                              41 seconds ago  Up 38 seconds ago  0.0.0.0:45678-&amp;gt;80/tcp  99e235dfe7a3-infra
aa4a4ba1af39  docker.io/library/httpd:latest  httpd-foreground  38 seconds ago  Up 38 seconds ago  0.0.0.0:45678-&amp;gt;80/tcp  apache_pod-apache
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到这里有几个发现：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;用 &lt;code&gt;podman pod ls&lt;/code&gt; 和 &lt;code&gt;podman ps&lt;/code&gt; 可以查看 Pod 和容器的情况&lt;/li&gt;
&lt;li&gt;Podman 取了个巧，使用命名的方式来区分容器和 Pod&lt;/li&gt;
&lt;li&gt;Podman 启动的 Pod 用到了 Infra 容器，所以一个 Pod 里面会有两个容器。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为了让后续动作顺利，可以把容器名称修改为 &lt;code&gt;httpd&lt;/code&gt;，用于消除这种隐式变更。在应用新版本 YAML 之前，需要因为发生了改名情况，所以无法使用 &lt;code&gt;podman play kube pod.yaml --down&lt;/code&gt; 的方式停止 Pod，这里用 &lt;code&gt;podman pod kill apache_pod &amp;amp;&amp;amp; podman pod rm apache_pod&lt;/code&gt; 删除 Pod，然后重新创建修改后的 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$podman play kube pod.yaml
Pod:
...
Container:
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;甚至可以启动一个 Deployment，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用 &lt;code&gt;play kube&lt;/code&gt; 运行一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ podman play kube deploy.yaml
...
podman pod ls
POD ID        NAME         STATUS      CREATED         INFRA ID      # OF CONTAINERS
0a6e4dcda93c  nginx-pod-2  Running     15 seconds ago  319f12f3b6f2  2
266df25c4df1  nginx-pod-1  Running     19 seconds ago  a65f6b601160  2
e6966f42c5fd  nginx-pod-0  Running     22 seconds ago  953e3e830528  2
573597e627ec  apache       Running     9 minutes ago   3b4ff4625b46  2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，这里生成了 3 个 &lt;code&gt;nginx-pod&lt;/code&gt; 为前缀的 Pod。&lt;/p&gt;

&lt;h3 id=&#34;configmap&#34;&gt;Configmap&lt;/h3&gt;

&lt;p&gt;修改一下刚才的 &lt;code&gt;pod.yaml&lt;/code&gt;，其中加入 Configmap：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: cm-sample
data:
  key1: value1
---
apiVersion: v1
kind: Pod
metadata:
  name: apache
spec:
  containers:
  - name: httpd
    image: httpd
    ports:
    - name: http
      containerPort: 80
      hostPort: 45678
    envFrom:
    - configMapRef:
        name: cm-sample
        optional: false
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里加入了一个引用 Configmap 作为环境变量的选项，使用 &lt;code&gt;--down&lt;/code&gt; 开关停止当前 Pod 并重建后（&lt;code&gt;4.x&lt;/code&gt; 版本有了 &lt;code&gt;--replace&lt;/code&gt; 开关），验证一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ podman exec -it [your container id] env | grep key
key1=value1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到已经成功引用了 Configmap。&lt;/p&gt;

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

&lt;p&gt;除了简单的运行功能之外，Podman Play 还提供了网络、命名空间等功能，甚至还有现场构建的能力，比孤零零的 &lt;code&gt;kubelet&lt;/code&gt; 强大不少，但是如果 Kubelet 加入 crictl、nerdctl 之类的东西的话，勉强也算各擅胜场。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>持续监控集群中的镜像漏洞——Trivy Operator 简介</title>
      <link>/post/intro-trivy-operator/</link>
      <pubDate>Sun, 19 Mar 2023 20:03:17 +0800</pubDate>
      <guid>/post/intro-trivy-operator/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;在本文中，我们将介绍 Trivy Operator，一款用于持续监控 Kubernetes 集群中的容器镜像漏洞的工具。我们将从 Trivy Operator 的简介开始，接着介绍如何安装和配置，最后探讨漏洞扫描与呈现，以及其他补充功能。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;引言&#34;&gt;引言&lt;/h2&gt;

&lt;p&gt;当下，容器技术已成为企业构建和部署应用的关键组成部分。然而，容器镜像可能会携带软件漏洞，这些漏洞可能导致应用和数据面临安全风险。为了确保 Kubernetes 集群在运行时的持续安全，就需要自动对运行中的容器镜像进行扫描的工具。&lt;/p&gt;

&lt;p&gt;很早以前曾经使用 Shell Operator 结合 Trivy 编写了一个小工具，对运行中的镜像进行扫描，然后把扫描结果用 Prometheus 的方式进行输出。&lt;/p&gt;

&lt;p&gt;接下来将要介绍的 Trivy-Operator，是一个来自 Aqua 的开源工具，可以自动扫描容器镜像中已知的漏洞，并用最佳实践对 Kubernetes 资源进行验证，从而提高 Kubernetes 集群的运行时安全性。它易于安装，可以顺利地集成到监控系统中；更借助Kubernetes Operator 技术响应集群上的工作负载和其他更改，自动更新安全报告资源。Trivy-Operator 能够显著加强 Kubernetes 集群的安全性，保护其中的应用程序免受潜在威胁。&lt;/p&gt;

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;在深入了解 Trivy-Operator 的使用方法之前，先简单交代一下它的大致功能：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;漏洞扫描：Trivy-Operator 基于 Trivy 扫描器，对容器镜像进行全面扫描，识别其中的已知漏洞。这有助于及时发现并修复潜在的安全隐患，保护您的应用程序免受攻击。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Kubernetes 资源验证：通过与 Kubernetes API 的集成，Trivy-Operator 可以自动验证 Kubernetes 资源的配置，确保遵循安全性最佳实践。这样可以避免因配置错误导致的安全风险。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;持续监控与报告：Trivy-Operator 自动更新安全报告资源，以响应 Kubernetes 集群上的工作负载和其他更改。这意味着它可以在创建新 Pod 时启动漏洞扫描和配置审核，然后更新扫描报告。这有助于实时了解集群安全状况，及时采取相应措施。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Prometheus 集成：Trivy-Operator 提供 Prometheus 指标端点，使其可以与现有的监控基础设施集成。通过Prometheus，用户可以收集和分析 Trivy-Operator 的指标数据，实现对集群安全的实时监控。通过 Prometheus 之后，还可以和 Grafana、Alert-manager 等联动，进一步提高集群的透明度和可运维性。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;可以通过三种方式安装和部署 Trivy Operator，YAML、Helm 和 OLM。&lt;/p&gt;

&lt;h3 id=&#34;yaml&#34;&gt;YAML&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f \
https://raw.githubusercontent.com/aquasecurity/trivy-operator/v0.12.1/deploy/static/trivy-operator.yaml

customresourcedefinition.apiextensions.k8s.io/clustercompliancereports.aquasecurity.github.io created
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，这里创建了几个 CRD，都是以 &lt;code&gt;reports&lt;/code&gt; 结尾的，看来都是各种报告，大概几个字面意思：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;infraassessmentreports，clusterinfraassessmentreports：基础设施评估报告，包括 Kubernetes 核心组件的配置内容&lt;/li&gt;
&lt;li&gt;vulnerabilityreports：漏洞报告&lt;/li&gt;
&lt;li&gt;configauditreports、clusterconfigauditreports：配置审计报告&lt;/li&gt;
&lt;li&gt;exposedsecretreports：Secret 报告&lt;/li&gt;
&lt;li&gt;clusterrbacassessmentreports/rbacassessmentreports：RBAC 评估报告&lt;/li&gt;
&lt;li&gt;clusterrbacassessmentreports：集群 RBAC 评估报告&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外还生成了一个叫做 &lt;code&gt;trivy-operator&lt;/code&gt; 的 ServiceAccount，可以查看一下它的权限：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl rolesum trivy-operator
ServiceAccount: trivy-system/trivy-operator
...
Policies:
...
• [CRB] */trivy-operator ⟶  [CR] */trivy-operator
  Resource                                                Name  Exclude  Verbs  G L W C U P D DC
  clustercompliancedetailreports.aquasecurity.github.io   [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
  clustercompliancereports.aquasecurity.github.io         [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;这里用到了 kubectl 的 &lt;a href=&#34;https://github.com/Ladicle/kubectl-rolesum&#34; target=&#34;_blank&#34;&gt;rolesum 插件&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以看到，它从 &lt;code&gt;trivy-operator&lt;/code&gt; 这个 &lt;code&gt;ClusterRole&lt;/code&gt; 继承了大量权限，除了前面提到的 CR 之外，还包括了对 Pod、Configmap 等的读取权限，据此可以判断他的工作范围。&lt;/p&gt;

&lt;h3 id=&#34;helm&#34;&gt;Helm&lt;/h3&gt;

&lt;p&gt;这个安装也比较简单，首先加入 Aqua 的仓库：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm repo add aqua https://aquasecurity.github.io/helm-charts
&amp;quot;aqua&amp;quot; has been added to your repositories
$ helm repo update
...
$ helm install trivy-operator aqua/trivy-operator \
  --namespace trivy-system \
  --create-namespace \
  --set=&amp;quot;trivy.ignoreUnfixed=true&amp;quot; \
  --version 0.12.1
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以用 &lt;code&gt;helm show values aqua/trivy-operator&lt;/code&gt; 看看其中包含的丰富配置。后面也会进行一点讲解。&lt;/p&gt;

&lt;h3 id=&#34;operator-lifecycle-manager&#34;&gt;Operator Lifecycle Manager&lt;/h3&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/operator-framework/operator-lifecycle-manager/&#34; target=&#34;_blank&#34;&gt;OLM&lt;/a&gt;这是一种专门用于维护 Operator 生命周期的方式。这里暂时不做更多介绍。具体安装方式可以参照&lt;a href=&#34;https://aquasecurity.github.io/trivy-operator/v0.12.1/getting-started/installation/olm/&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;配置&#34;&gt;配置&lt;/h2&gt;

&lt;p&gt;Operator Pod（&lt;code&gt;trivy-operator-&lt;/code&gt;）支持很多环境变量用于其行为配置，下面列出一些关键内容：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_EXCLUDE_NAMESPACES&lt;/code&gt;：排除命名空间&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_VULNERABILITY_SCANNER_ENABLED&lt;/code&gt;：启用漏洞扫描&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_CONFIG_AUDIT_SCANNER_ENABLED&lt;/code&gt;：启用配置审计&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_RBAC_ASSESSMENT_SCANNER_ENABLED&lt;/code&gt;：启用 RBAC 扫描&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_CONFIG_AUDIT_SCANNER_BUILTIN&lt;/code&gt;：启用内置的配置扫描引擎&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_WEBHOOK_BROADCAST_URL&lt;/code&gt;：Webhook 地址，置空则禁用该功能&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外，同一个命名空间内还有一个 Configmap，内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
data:
  trivy.additionalVulnerabilityReportFields: &amp;quot;&amp;quot;
  trivy.command: image
  trivy.dbRepository: ghcr.io/aquasecurity/trivy-db
  trivy.dbRepositoryInsecure: &amp;quot;false&amp;quot;
  trivy.mode: Standalone
  trivy.repository: ghcr.io/aquasecurity/trivy
  trivy.resources.limits.cpu: 500m
  trivy.resources.limits.memory: 500M
  trivy.resources.requests.cpu: 100m
  trivy.resources.requests.memory: 100M
  trivy.severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL
  trivy.slow: &amp;quot;true&amp;quot;
  trivy.supportedConfigAuditKinds: Workload,Service,Role,ClusterRole,NetworkPolicy,Ingress,LimitRange,ResourceQuota
  trivy.tag: 0.38.2
  trivy.timeout: 5m0s
  trivy.useBuiltinRegoPolicies: &amp;quot;true&amp;quot;
kind: ConfigMap
metadata:
  annotations:
    ...
  name: trivy-operator-trivy-config
  namespace: trivy-system
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中的内容，熟悉 Trivy 扫描器的读者应该很容易看得出来——这里基本定义了最常用的几个 Trivy 开关。另外根据官网看来，还可以使用 &lt;code&gt;trivy-operator-trivy-config&lt;/code&gt; Secret 的 &lt;code&gt;data.trivy.githubToken&lt;/code&gt; 来设置用于抓取 Trivy 特征库的 Github Token。&lt;/p&gt;

&lt;h2 id=&#34;漏洞扫描和呈现&#34;&gt;漏洞扫描和呈现&lt;/h2&gt;

&lt;p&gt;事实上，Trivy Operator 部署之后直接就会启动扫描，生成漏洞报告（vulnerabilityreports）以及 RBAC 报告（rbacassessment），可以使用 &lt;code&gt;kubectl get xx yy-o wide&lt;/code&gt;，或者 &lt;code&gt;kubectl descrbe xx yy&lt;/code&gt; 来查看具体内容。例如漏洞报告显示各级别问题都是 &lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;新建一个工作负载，例如 &lt;code&gt;kubectl create deployment nginx --image nginx:1.16&lt;/code&gt;，创建之后，会发现马上出现一个 &lt;code&gt;scan-vulnerabilityreport-*&lt;/code&gt; 的 Pod 启动了，在它完成任务消失之后，我们会看到 &lt;code&gt;vulns&lt;/code&gt; 多了一条针对  &lt;code&gt;nginx:1.16&lt;/code&gt; 镜像的记录，其中包含高中低各种级别的漏洞若干。&lt;/p&gt;

&lt;p&gt;另外还新出现了一个名为 &lt;code&gt;replicaset-nginx-XXX&lt;/code&gt; 的 &lt;code&gt;ConfigAuditReport&lt;/code&gt; 对象，其中包含了对这个 RS 的审计内容，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  - category: Kubernetes Security Check
    checkID: KSV015
    description: When containers have resource requests specified, the scheduler can
      make better decisions about which nodes to place pods on, and how to deal with
      resource contention.
    messages:
    - Container &#39;nginx&#39; of ReplicaSet &#39;nginx-54f8f9f495&#39; should set &#39;resources.requests.cpu&#39;
    severity: LOW
    success: false
    title: CPU requests not specified
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这些基本内容都可以通过 Prometheus 监控栈进行监控，并可通过 Grafana &lt;a href=&#34;https://github.com/dotdc/grafana-dashboards-kubernetes/blob/master/dashboards/trivy&#34; target=&#34;_blank&#34;&gt;Dashboard&lt;/a&gt; 进行可视化呈现；或者用 Alert Manager 以及 Webhook 进行告警。&lt;/p&gt;

&lt;h2 id=&#34;补充&#34;&gt;补充&lt;/h2&gt;

&lt;p&gt;其实除了 YAML 和镜像漏洞的检查之外，这个 Operator 还定义了多种合规性、安全基线方面的内容，并可以通过 REGO 语言进行自定义的基线检查。虽然多数功能还处于非正式版本，但这是一个合理的方向———对集群安全，要进行可视化的、持续的审视，而不是。。我不说了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>用 ChatGPT 写了一点代码</title>
      <link>/post/write-ingress-watcher-with-chatgpt/</link>
      <pubDate>Sun, 19 Feb 2023 21:14:35 +0800</pubDate>
      <guid>/post/write-ingress-watcher-with-chatgpt/</guid>
      <description>

&lt;p&gt;ChatGPT 发布之后，一直在半真半假的用着：有时候写一些代码片段，例如让他用 Python、Rust 分别帮我写一点方法级别的代码；有时候会跟他“探讨”一些问题——是的你没看错，真的是探讨一些问题，例如刚刚我跟他探讨了一下 SRE、DevOps 和平台工程的区别、联系和发展情况。总的说来，如果不求输出成品，那效果还是相当不错的，至少触发灵感是没问题的。&lt;/p&gt;

&lt;p&gt;前些天跟蔡书夸下海口，用 ChatGPT 也能给 PP 写个 Ingress 控制器来，奈何俗务缠身，一直也没能完成，今天抽出两个小时，总算完成了一个 Ingress 监听的原型代码。大体过程讲解一下。&lt;/p&gt;

&lt;p&gt;我的第一个 Prompt：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;我想用循序渐进的方式逐步用 Golang 开发一个 Ingress 控制器，我想到的步骤包括：

1. 创建目录布局 
2. 编写第一个可执行的控制台应用的圆形，并完成构建和测试运行 
3. 在这个原型基础上，加入连接 Kubernetes 集群的能力，同样完成构建和运行 
4. 加入 Watch Ingress 对象的能力。并在 Ingress 发生变化的时候输出提示信息。
首先我们完成第一个步骤，使用 shell 命令创建对应的目录结构。

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;是的，你没看错，我连目录结构都让他给我创建。ChatGPT 的回复很不错——必须很不错，这个太简单了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;mkdir -p my-ingress-controller/cmd/my-ingress-controller
mkdir -p my-ingress-controller/pkg/ingress
mkdir -p my-ingress-controller/pkg/kube
touch my-ingress-controller/cmd/my-ingress-controller/main.go
touch my-ingress-controller/pkg/ingress/ingress.go
touch my-ingress-controller/pkg/kube/kube.go
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;并且给出了解释：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这将创建一个名为 &lt;code&gt;my-ingress-controller&lt;/code&gt; 的目录，并在其中创建 &lt;code&gt;cmd&lt;/code&gt; 和 &lt;code&gt;pkg&lt;/code&gt; 目录，分别用于存放主应用程序和包。此外，我们在 &lt;code&gt;cmd/my-ingress-controller&lt;/code&gt; 目录中创建了 &lt;code&gt;main.go&lt;/code&gt; 文件，用于编写第一个可执行的控制台应用程序的圆形；在 &lt;code&gt;pkg/ingress&lt;/code&gt; 和 &lt;code&gt;pkg/kube&lt;/code&gt; 目录中，分别创建了 &lt;code&gt;ingress.go&lt;/code&gt; 和 &lt;code&gt;kube.go&lt;/code&gt; 文件，用于存放后续的代码。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我照章办事执行之后，当然生成了对应的目录和文件，接下来我给出第二个 Prompt：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;然后完成第二步骤，完成一个可执行的控制台应用的圆形。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ChatGPT 给出了经典的 Hello World 代码，填充了 &lt;code&gt;my-ingress-controller/cmd/my-ingress-controller/main.go&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;package main

import (
    &amp;quot;fmt&amp;quot;
)

func main() {
    fmt.Println(&amp;quot;Hello, Ingress!&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接着他解释了代码的作用，提示使用 &lt;code&gt;go build -o my-ingress-controller ./cmd/my-ingress-controller&lt;/code&gt; 命令进行构建。&lt;/p&gt;

&lt;p&gt;对此我的回复是&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;go build 执行失败了，返回信息“go: go.mod file not found in current directory or any parent directory; see &#39;go help modules&#39;”&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ChatGPT 回应大意是：&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;这是因为 Go 1.11 版本后引入了模块（module）的概念，用于解决依赖管理的问题。如果我们的项目使用了 Go 模块，则需要在项目根目录中创建一个 go.mod 文件来记录项目的依赖关系。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;go mod init my-ingress-controller&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果你使用的是 Go 1.16 及以上的版本，也可以使用以下命令自动初始化 Go 模块：&lt;code&gt;go mod init&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;照章办事之后，果然构建通过了。&lt;/p&gt;

&lt;p&gt;运行成功后，我们开始连接 Kubernetes，对此我给出的 Prompt 是：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这一步运行成功之后，我们完成第三个步骤，加入连接 Kubernetes 集群的代码。并再次进行构建和试运行。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ChatGPT 并未含糊，给出了 &lt;code&gt;kube.go&lt;/code&gt; 和 &lt;code&gt;main.go&lt;/code&gt; 的代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// kube.go 两种方法进行连接
package kube

import (
    &amp;quot;k8s.io/client-go/kubernetes&amp;quot;
    &amp;quot;k8s.io/client-go/tools/clientcmd&amp;quot;
    &amp;quot;k8s.io/client-go/rest&amp;quot;
)

func NewClient() (*kubernetes.Clientset, error) {
    config, err := clientcmd.BuildConfigFromFlags(&amp;quot;&amp;quot;, clientcmd.RecommendedHomeFile)
    if err != nil {
        return nil, err
    }
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        return nil, err
    }
    return clientset, nil
}

func InClusterConfig() (*rest.Config, error) {
    return rest.InClusterConfig()
}

//main.go：列出 Namespace

package main

import (
    &amp;quot;fmt&amp;quot;
    &amp;quot;my-ingress-controller/pkg/kube&amp;quot;
)

func main() {
    client, err := kube.NewClient()
    if err != nil {
        fmt.Println(err)
        return
    }
    namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
    if err != nil {
        fmt.Println(err)
        return
    }
    for _, ns := range namespaces.Items {
        fmt.Println(ns.Name)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在这个步骤之后，ChatGPT 开始不太灵了，主要出现的问题包括：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client-Go 和 Kubernetes 之间存在对应关系，他对于这方面并没有一个稳定的假设，导致 API 的兼容频频出错&lt;/li&gt;
&lt;li&gt;偶尔会在 Import 方面出现疏漏&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Import 问题基本上来说，贴出构建错误就能解决；而版本问题则需要反复调试和提示才得以解决。&lt;/p&gt;

&lt;p&gt;最后的一个坑是，他给我生成的 Informer 语句如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;informerFactory := informers.NewSharedInformerFactoryWithOptions(
    client, 
    0,
    informers.WithNamespace(&amp;quot;default&amp;quot;),
    informers.WithTweakListOptions(func(options *metav1.ListOptions) {
        options.LabelSelector = &amp;quot;app=my-ingress-controller&amp;quot;
    }),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里暗含了对命名空间和标签的要求，导致测试无法通过。在我发现问题发出疑问时，这位很淡定的回答：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;是的，如果要测试 Ingress 资源的监控，可以在创建 Ingress 资源的时候添加一个 &lt;code&gt;app=my-ingress-controller&lt;/code&gt; 的标签，这样就会触发事件并输出日志信息。&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;所以以后学编程，是不是可以不用费劲加那么多啥微信群了？&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>用 SPIRE 为 Pod 提供身份</title>
      <link>/post/k8s-workload-id-with-spire/</link>
      <pubDate>Thu, 24 Nov 2022 21:55:45 +0800</pubDate>
      <guid>/post/k8s-workload-id-with-spire/</guid>
      <description>

&lt;h2 id=&#34;开始之前&#34;&gt;开始之前&lt;/h2&gt;

&lt;p&gt;SPIFFE 是一个认证框架，能为多种节点和工作负载类型提供证实能力，解决“我是我”的问题，前面文章演示过用 SPIRE 给类 Unix 进程提供身份的方法，今天这篇就试试给 Pod 提供身份。&lt;/p&gt;

&lt;p&gt;这次实验会在前面的基础之上，在 Kubernetes 集群之外运行独立的 SPIRE Server，在集群中用 Pod 的形式运行 SPIRE Agent 作为节点，最后在其它 Pod 中访问 SPIRE Agent，获取 SVID。本文所涉及的对象关系如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/spiff_and_k8s.drawio.png&#34; alt=&#34;spiff and k8s&#34; /&gt;&lt;/p&gt;

&lt;p&gt;开始之前，需要做一些准备：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;有一个 Kubernetes 集群，Kind 或者 Minikube 也都是可以完成测试的。&lt;/li&gt;
&lt;li&gt;SPIRE 1.5.x 的二进制文件，可以从 &lt;code&gt;https://spiffe.io/downloads/&lt;/code&gt; 下载&lt;/li&gt;
&lt;li&gt;构建镜像所需的基础镜像和 Podman/Docker 等工具。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;kubernetes-相关插件&#34;&gt;Kubernetes 相关插件&lt;/h2&gt;

&lt;p&gt;这里要用到 SPIRE 的三个插件：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes Node Attestor&lt;/strong&gt;：用于证实 Node 身份，需要分别在 Server 和 Agent 两侧进行配置。目前可以选择 &lt;code&gt;k8s_sat&lt;/code&gt; 或者 &lt;code&gt;k8s_psat&lt;/code&gt; 两种插件，两侧的插件选择应保持一致，分别用于 ServiceAccount Token 和新版本 Kubernetes 中新增的 Projected ServiceAccount Token，本文选择的是 &lt;code&gt;k8s_sat&lt;/code&gt;。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Projected Token 具有更好的安全性，延伸阅读：&lt;a href=&#34;https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/&#34; target=&#34;_blank&#34;&gt;https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/&lt;/a&gt;）&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes Bundle&lt;/strong&gt;：Trust Bundle 是数字证书的集合，在 Kubernetes 中往往需要使用 Configmap 来存储和共享，所以一个直接的想法就是通过 &lt;code&gt;spire-server bundle show&lt;/code&gt; 命令来获取证书集合，并生成 Configmap。但是这个插件可以方便地通过 Kubernetes API 来自动维护证书集合到 Configmap 的转换过程，并自动完成轮转工作。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes Workload Attestor&lt;/strong&gt;：用于证实 Workload 身份，只需要在 Agent 中配置即可。&lt;/p&gt;

&lt;h2 id=&#34;配置和启动-spire-server&#34;&gt;配置和启动 SPIRE Server&lt;/h2&gt;

&lt;p&gt;简单粗暴上配置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;server {
...
    }
}

plugins {
    DataStore &amp;quot;sql&amp;quot; {
...
    }

KeyManager &amp;quot;disk&amp;quot; {
...
}

    Notifier &amp;quot;k8sbundle&amp;quot; {
        plugin_data {
            kube_config_file_path = &amp;quot;/home/dustise/.kube/config&amp;quot;
        }
    }

    NodeAttestor &amp;quot;k8s_sat&amp;quot; {
        plugin_data {
            clusters = {
                &amp;quot;kindcluster&amp;quot; = {
                    service_account_allow_list = [&amp;quot;spire:spire-agent&amp;quot;]
                    use_token_review_api_validation = true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的 SPIRE Server 配置中，省略了通用部分，具体内容可以参考前面一篇文章，重点看一下两节 Kubernetes 相关配置。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;k8sbundle&lt;/code&gt; 的作用就是把 Trust Bundle 内容保存到 Configmap 里面，因此是需要和 API Server 打交道的，这里给他直接配置了一个 KubeConfig 文件，访问方式还有其他的配置内容，可以参考&lt;a href=&#34;https://github.com/spiffe/spire/blob/main/doc/plugin_server_notifier_k8sbundle.md&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;。要注意的是，这里使用的 KubeConfig 文件所包含的账号是 Cluster Admin 权限，如果使用其他的账号，需要具备对 Configmap 进行 create 和 patch 操作的授权。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;k8s_sat&lt;/code&gt; 一节中，&lt;code&gt;clusters&lt;/code&gt; 字段是一个 Map，其中可以对接多个 Kubernetes 集群，这里我们填充了三个字段：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;service_account_allow_list&lt;/code&gt;：允许 Agent 注册时使用的 Service Account。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;use_token_review_api_validation&lt;/code&gt;：使用 TokenReview API 对 Serivce Account Token 进行验证，除此之外，还可以使用证书进行认证。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kube_config_file&lt;/code&gt;：和 API Server 进行沟通的凭据。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;和 Bundle 类似，这里同样需要具备一定的权限来完成 SPIRE Server 的工作，&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configmap 的 patch、get、list&lt;/li&gt;
&lt;li&gt;tokenreviews 的 create&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;创建好配置文件之后，可以先在目标集群中创建 &lt;code&gt;spire&lt;/code&gt; 命名空间。使用 &lt;code&gt;spire-server -config=[config file path]&lt;/code&gt; 命令启动服务器。稍后会在集群中看到新建的 Configmap。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;更多配置信息可以参考&lt;a href=&#34;https://github.com/spiffe/spire/blob/main/doc/plugin_server_nodeattestor_k8s_sat.md&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Server 启动成功后，可以提前为工作负载创建 Node 和 Entry：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;spire-server entry create -socketPath=socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/clusters/kindcluster \
    -selector k8s_sat:cluster:kindcluster -node

spire-server entry create -socketPath=socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/ns/default/sa/default \
    -parentID spiffe://spiffe.dom/ns/spire/sa/spire-agent \
    -selector k8s:ns:default \
    -selector k8s🈂️default
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;首先用 &lt;code&gt;k8s_sat:cluster:kindcluster&lt;/code&gt; 创建了一个在 &lt;code&gt;spiffe.dom&lt;/code&gt; 中的 Node 条目，它的 SPIFFE ID 是 &lt;code&gt;spiffe://spiffe.dom/clusters/kindcluster&lt;/code&gt;；&lt;/p&gt;

&lt;p&gt;接下来以 Node 条目为上级，使用 &lt;code&gt;k8s:ns:default&lt;/code&gt; + &lt;code&gt;k8s🈂️default&lt;/code&gt; 的 Selector，创建一个 SPIFFE ID &lt;code&gt;spiffe://spiffe.dom/ns/default/sa/default&lt;/code&gt;，代表在 &lt;code&gt;default&lt;/code&gt; 命名空间中用 &lt;code&gt;default&lt;/code&gt; Service Account 身份运行的 Pod。&lt;/p&gt;

&lt;h2 id=&#34;创建-agent&#34;&gt;创建 Agent&lt;/h2&gt;

&lt;p&gt;在运行 Agent 之前，首先要制作一个镜像，这里偷懒的使用现成二进制进行构建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Dockerfile&#34;&gt;FROM busybox:1.35.0-glibc
RUN mkdir -p /spire/bin
COPY spire-agent /spire/bin
CMD [&amp;quot;/spire/bin/spire-agent&amp;quot;, &amp;quot;-config=/spire/conf/k8s-agent.conf&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里要创建一个 Agent 的工作负载，为了让 Agent 能够通过进程号查询工作负载的 Pod 信息，并对工作负载提供 Workload API，需要满足几个条件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent 需要有授权访问 Kubernetes 的特定资源&lt;/li&gt;
&lt;li&gt;共享 Socket 文件，让 Workload 可以访问 Agent 提供的 Workload API&lt;/li&gt;
&lt;li&gt;能够识别调用 Workload API 的进程的 Pod 信息，从而生成 Selector&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;综合以上考虑，我们需要设计这样的 Workload：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用主机卷的方式在每个节点上暴露 Socket&lt;/li&gt;
&lt;li&gt;能够访问 Trust Bundle 所在的 Configmap&lt;/li&gt;
&lt;li&gt;Agent 和 Workload 共享 IPC 空间，便于通过进程号识别身份&lt;/li&gt;
&lt;li&gt;Agent 所使用的 Service Account 需要具备和 API Server/Kubelet 通信查询信息的能力。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此产生如下的 YAML 片段：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;    spec:
      hostPID: true
      hostNetwork: true
      serviceAccountName: spire-agent
...
      containers:
        - name: spire-agent
          image: gcr.io/spiffe-io/spire-agent:1.5.0
          args: [&amp;quot;-config&amp;quot;, &amp;quot;/run/spire/config/agent.conf&amp;quot;]
          volumeMounts:
            - name: spire-config
              mountPath: /run/spire/config
              readOnly: true
            - name: spire-bundle
              mountPath: /run/spire/bundle
            - name: spire-agent-socket
              mountPath: /run/spire/sockets
              readOnly: false        
      volumes:
        - name: spire-config
          configMap:
            name: spire-agent
        - name: spire-bundle
          configMap:
            name: spire-bundle
        - name: spire-agent-socket
          hostPath:
            path: /run/spire/sockets
            type: DirectoryOrCreate
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段 YAML 有几个要点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;使用了符合 SPIRE Server 配置中要求的 ServiceAccount&lt;/li&gt;
&lt;li&gt;HostPID 共享主机 PID 空间&lt;/li&gt;
&lt;li&gt;HostNetwork 共享主机网络空间&lt;/li&gt;
&lt;li&gt;加载 Trust Bundle 所在的 Configmap&lt;/li&gt;
&lt;li&gt;加载一个主机卷用于输出 Socket 文件&lt;/li&gt;
&lt;li&gt;用一个 Configmap 保存配置文件并加载&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent 的配置文件如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;agent {
  data_dir = &amp;quot;/run/spire&amp;quot;
  log_level = &amp;quot;DEBUG&amp;quot;
  server_address = &amp;quot;10.211.55.5&amp;quot;
  server_port = &amp;quot;8081&amp;quot;
  socket_path = &amp;quot;/run/spire/sockets/agent.sock&amp;quot;
  trust_bundle_path = &amp;quot;/run/spire/bundle/bundle.crt&amp;quot;
  trust_domain = &amp;quot;spiffe.dom&amp;quot;
}

plugins {
  NodeAttestor &amp;quot;k8s_sat&amp;quot; {
    plugin_data {
      cluster = &amp;quot;kindcluster&amp;quot;
    }
  }

  KeyManager &amp;quot;memory&amp;quot; {
...
  }

  WorkloadAttestor &amp;quot;k8s&amp;quot; {
    plugin_data {
      skip_kubelet_verification = true
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Agent 配置相对来说稍显复杂：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;server_address&lt;/code&gt; 和 &lt;code&gt;server_pod&lt;/code&gt;，用于访问前面启动的 SPIRE SERVER&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trust_bundle_path&lt;/code&gt; 引用 Configmap 的加载路径即可&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trust_domain&lt;/code&gt; 需要保持和 SPIRE Server 定义一致&lt;/li&gt;
&lt;li&gt;&lt;code&gt;k8s_sat&lt;/code&gt; 的 &lt;code&gt;cluster&lt;/code&gt; 字段中，集群名称需要和 SPIRE Server 的 Map 中的定义匹配&lt;/li&gt;
&lt;li&gt;&lt;code&gt;skip_kubelet_verification&lt;/code&gt;：跳过对 Kubelet 证书的检查&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent 使用的 Service Account 也需要进行 RBAC 授权，需要能够对 &lt;code&gt;pod&lt;/code&gt;、&lt;code&gt;node&lt;/code&gt; 以及 &lt;code&gt;node/proxy&lt;/code&gt; 进行 &lt;code&gt;get&lt;/code&gt; 操作。&lt;/p&gt;

&lt;p&gt;先后把配置 Configmap、RBAC 以及 Daemonset 等资源提交之后，会看到 Agent Pod 启动。&lt;/p&gt;

&lt;h2 id=&#34;启动客户端&#34;&gt;启动客户端&lt;/h2&gt;

&lt;p&gt;任意启动一个客户端程序，为模仿接入 Workload API 的实现，其中还是需要使用 SPIRE Agent 的二进制。客户端应该使用 Agent 的 Socket 访问 Wokrload API，同时为了表明身份，同样需要用 &lt;code&gt;HostPID&lt;/code&gt; 供 Agent 识别，因此运行如下工作负载：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
      hostPID: true
      hostNetwork: true
...
      containers:
        - name: client
          image: gcr.io/spiffe-io/spire-agent:1.2.3
          command: [&amp;quot;sleep&amp;quot;]
          args: [&amp;quot;1000000000&amp;quot;]
          volumeMounts:
            - name: spire-agent-socket
              mountPath: /run/spire/sockets
              readOnly: true
      volumes:
        - name: spire-agent-socket
          hostPath:
            path: /run/spire/sockets
            type: Directory
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 在 &lt;code&gt;default&lt;/code&gt; 命名空间启动之后，进入 Shell 使用 &lt;code&gt;spire-agent api fetch&lt;/code&gt; 命令，就能成功的获取 SVID 了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bin/spire-agent api  fetch -socketPath=/run/spire/sockets/agent.sock
Received 1 svid after 83.772792ms

SPIFFE ID:              spiffe://spiffe.dom/shutup
SVID Valid After:       2022-11-24 17:02:03 +0000 UTC
SVID Valid Until:       2022-11-24 17:04:13 +0000 UTC
CA #1 Valid After:      2022-11-23 14:57:51 +0000 UTC
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;to-be-continued&#34;&gt;To be continued&lt;/h2&gt;

&lt;p&gt;现在我们就用一个非常笨拙的方法，把 Kubernetes 的工作负载识别能力接入到了 SPIRE Server 里面了。事实上接入 Kubernetes 还有别的部署和使用方式，例如使用 CRD、在集群内运行 SPIRE Server、使用 Envoy 等接入 Workload API 等。官网文档中对这些案例都有较为详细的指导。&lt;/p&gt;

&lt;p&gt;结合前面对于 Ghostunel 等的介绍，不难看出，打通虚拟机和 Kubernetes 工作负载身份是可行的，而根据联邦一文的描述，这个体系还可以和 OIDC 等进行互通，进一步扩大 SPIFFE SVID 的版图。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Prometheus 和 Pod 标签</title>
      <link>/post/prometheus-and-pod-label/</link>
      <pubDate>Fri, 22 Jul 2022 09:48:06 +0800</pubDate>
      <guid>/post/prometheus-and-pod-label/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://5pi.de/2017/11/09/use-prometheus-vector-matching-to-get-kubernetes-utilization-across-any-pod-label/&#34; target=&#34;_blank&#34;&gt;Use Prometheus Vector Matching to get Kubernetes Utilization across any Pod Label&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://5pi.de/&#34; target=&#34;_blank&#34;&gt;Johannes Ziemke&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注：这里实际上涉及到两种标签，一个是 Pod 的，一个是 Metrics 的，非常容易混淆，所以会分别写成 Pod 标签和指标标签。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Prometheus 是为 Kubernetes 这样的动态环境而生的。它的服务发现能力和查询语言非常强大，Kubernetes 运维过程中，用户可以借 Prometheus 解决监控问题。&lt;/p&gt;

&lt;p&gt;相对其它竞品来说，这种弹性直接提高了 Prometheus 的使用门槛，&lt;a href=&#34;https://prometheus.io/docs/prometheus/latest/querying/operators/#vector-matching&#34; target=&#34;_blank&#34;&gt;向量匹配&lt;/a&gt; 就是众多拦路虎中的一个。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://prometheus.io/docs/prometheus/latest/querying/operators/#vector-matching&#34; target=&#34;_blank&#34;&gt;Prometheus 文档&lt;/a&gt;中在这个主题上做了非常精彩的阐述，所以本文中不会做过多的细节阐述，而是会围绕资源使用率这个主题进行一些场景化的尝试。&lt;/p&gt;

&lt;h2 id=&#34;用标签聚合内存用量&#34;&gt;用标签聚合内存用量&lt;/h2&gt;

&lt;p&gt;Kubernetes 提供了一个 &lt;code&gt;container_memory_usage_bytes&lt;/code&gt; 指标，用于表达 Pod 的内存用量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;...
container_memory_usage_bytes{beta_kubernetes_io_arch=&amp;quot;amd64&amp;quot;,beta_kubernetes_io_fluentd_ds_ready=&amp;quot;true&amp;quot;,beta_kubernetes_io_instance_type=&amp;quot;g1-small&amp;quot;,beta_kubernetes_io_os=&amp;quot;linux&amp;quot;,cloud_google_com_gke_nodepool=&amp;quot;small-preemptible&amp;quot;,cloud_google_com_gke_preemptible=&amp;quot;true&amp;quot;,container_name=&amp;quot;POD&amp;quot;,failure_domain_beta_kubernetes_io_region=&amp;quot;us-east1&amp;quot;,failure_domain_beta_kubernetes_io_zone=&amp;quot;us-east1-c&amp;quot;,id=&amp;quot;/kubepods/burstable/pod13d4221c-c484-11e7-bff5-42010af0018b/67e5bb069ab9881ff8a55b8628ef4935b0d1ace09c18df20db059522bdfd5b7d&amp;quot;,image=&amp;quot;gcr.io/google_containers/pause-amd64:3.0&amp;quot;,instance=&amp;quot;gke-latency-at-small-preemptible-0c981b61-9489&amp;quot;,job=&amp;quot;kubernetes-cadvisor&amp;quot;,kubernetes_io_hostname=&amp;quot;gke-latency-at-small-preemptible-0c981b61-9489&amp;quot;,name=&amp;quot;k8s_POD_latency-api-971504058-jzs5h_default_13d4221c-c484-11e7-bff5-42010af0018b_0&amp;quot;,namespace=&amp;quot;default&amp;quot;,pod_name=&amp;quot;latency-api-971504058-jzs5h&amp;quot;}	389120
container_memory_usage_bytes{beta_kubernetes_io_arch=&amp;quot;amd64&amp;quot;,beta_kubernetes_io_fluentd_ds_ready=&amp;quot;true&amp;quot;,beta_kubernetes_io_instance_type=&amp;quot;g1-small&amp;quot;,beta_kubernetes_io_os=&amp;quot;linux&amp;quot;,cloud_google_com_gke_nodepool=&amp;quot;small-preemptible&amp;quot;,cloud_google_com_gke_preemptible=&amp;quot;true&amp;quot;,container_name=&amp;quot;POD&amp;quot;,failure_domain_beta_kubernetes_io_region=&amp;quot;us-east1&amp;quot;,failure_domain_beta_kubernetes_io_zone=&amp;quot;us-east1-c&amp;quot;,id=&amp;quot;/kubepods/burstable/pod81d0f651-c500-11e7-bff5-42010af0018b/309e05b118e618122c70ccf88538d13ca41c3b5a770d5d67882426854391c23c&amp;quot;,image=&amp;quot;gcr.io/google_containers/pause-amd64:3.0&amp;quot;,instance=&amp;quot;gke-latency-at-small-preemptible-0c981b61-9489&amp;quot;,job=&amp;quot;kubernetes-cadvisor&amp;quot;,kubernetes_io_hostname=&amp;quot;gke-latency-at-small-preemptible-0c981b61-9489&amp;quot;,name=&amp;quot;k8s_POD_latency-api-971504058-gszpw_default_81d0f651-c500-11e7-bff5-42010af0018b_0&amp;quot;,namespace=&amp;quot;default&amp;quot;,pod_name=&amp;quot;latency-api-971504058-gszpw&amp;quot;}	372736
container_memory_usage_bytes{beta_kubernetes_io_arch=&amp;quot;amd64&amp;quot;,beta_kubernetes_io_fluentd_ds_ready=&amp;quot;true&amp;quot;,beta_kubernetes_io_instance_type=&amp;quot;g1-small&amp;quot;,beta_kubernetes_io_os=&amp;quot;linux&amp;quot;,cloud_google_com_gke_nodepool=&amp;quot;small-preemptible&amp;quot;,cloud_google_com_gke_preemptible=&amp;quot;true&amp;quot;,container_name=&amp;quot;latency-api&amp;quot;,failure_domain_beta_kubernetes_io_region=&amp;quot;us-east1&amp;quot;,failure_domain_beta_kubernetes_io_zone=&amp;quot;us-east1-c&amp;quot;,id=&amp;quot;/kubepods/burstable/pod13d4221c-c484-11e7-bff5-42010af0018b/497e6fdf2217771cb3f52e6fef93734d023f0e7f23f92c58d22139fc18dc5f13&amp;quot;,image=&amp;quot;registry.gitlab.com/latency.at/latencyat@sha256:8ea057e064b64cc9c8459a68ef3f6d0fc26169b4f57aef193831779e1fe713d4&amp;quot;,instance=&amp;quot;gke-latency-at-small-preemptible-0c981b61-9489&amp;quot;,job=&amp;quot;kubernetes-cadvisor&amp;quot;,kubernetes_io_hostname=&amp;quot;gke-latency-at-small-preemptible-0c981b61-9489&amp;quot;,name=&amp;quot;k8s_latency-api_latency-api-971504058-jzs5h_default_13d4221c-c484-11e7-bff5-42010af0018b_1&amp;quot;,namespace=&amp;quot;default&amp;quot;,pod_name=&amp;quot;latency-api-971504058-jzs5h&amp;quot;}	11014144
container_memory_usage_bytes{beta_kubernetes_io_arch=&amp;quot;amd64&amp;quot;,beta_kubernetes_io_fluentd_ds_ready=&amp;quot;true&amp;quot;,beta_kubernetes_io_instance_type=&amp;quot;g1-small&amp;quot;,beta_kubernetes_io_os=&amp;quot;linux&amp;quot;,cloud_google_com_gke_nodepool=&amp;quot;small-preemptible&amp;quot;,cloud_google_com_gke_preemptible=&amp;quot;true&amp;quot;,container_name=&amp;quot;latency-api&amp;quot;,failure_domain_beta_kubernetes_io_region=&amp;quot;us-east1&amp;quot;,failure_domain_beta_kubernetes_io_zone=&amp;quot;us-east1-c&amp;quot;,id=&amp;quot;/kubepods/burstable/pod81d0f651-c500-11e7-bff5-42010af0018b/7b438a8e9df0cf1ab29d067fd36c97099f9f5e7e9257f6187c5be6bff846a62c&amp;quot;,image=&amp;quot;registry.gitlab.com/latency.at/latencyat@sha256:8ea057e064b64cc9c8459a68ef3f6d0fc26169b4f57aef193831779e1fe713d4&amp;quot;,instance=&amp;quot;gke-latency-at-small-preemptible-0c981b61-9489&amp;quot;,job=&amp;quot;kubernetes-cadvisor&amp;quot;,kubernetes_io_hostname=&amp;quot;gke-latency-at-small-preemptible-0c981b61-9489&amp;quot;,name=&amp;quot;k8s_latency-api_latency-api-971504058-gszpw_default_81d0f651-c500-11e7-bff5-42010af0018b_0&amp;quot;,namespace=&amp;quot;default&amp;quot;,pod_name=&amp;quot;latency-api-971504058-gszpw&amp;quot;}	11448320
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;但是很不幸，这其中并不包含 Pod 标签。还好，&lt;a href=&#34;https://github.com/kubernetes/kube-state-metrics&#34; target=&#34;_blank&#34;&gt;kube-state-metrics&lt;/a&gt; 提供了一个 &lt;code&gt;kube_pod_labels&lt;/code&gt; 指标，这个指标包含一个静态时序，其中表达了 Pod 标签和 Pod 名称的关系：&lt;/p&gt;

&lt;p&gt;可以用 &lt;code&gt;(pod_name=&amp;quot;latency-api-971504058-jzs5h&amp;quot;)&lt;/code&gt; 来查询 Pod 的标签：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;kube_pod_labels{instance=&amp;quot;10.116.0.12:8080&amp;quot;,job=&amp;quot;kubernetes-service-endpoints&amp;quot;,k8s_app=&amp;quot;kube-state-metrics&amp;quot;,kubernetes_name=&amp;quot;kube-state-metrics&amp;quot;,kubernetes_namespace=&amp;quot;kube-system&amp;quot;,label_app=&amp;quot;latency-api&amp;quot;,label_pod_template_hash=&amp;quot;971504058&amp;quot;,namespace=&amp;quot;default&amp;quot;,pod=&amp;quot;latency-api-971504058-jzs5h&amp;quot;} 1
kube_pod_labels{instance=&amp;quot;10.116.1.26:8080&amp;quot;,job=&amp;quot;kubernetes-service-endpoints&amp;quot;,k8s_app=&amp;quot;kube-state-metrics&amp;quot;,kubernetes_name=&amp;quot;kube-state-metrics&amp;quot;,kubernetes_namespace=&amp;quot;kube-system&amp;quot;,label_app=&amp;quot;latency-api&amp;quot;,label_pod_template_hash=&amp;quot;971504058&amp;quot;,namespace=&amp;quot;default&amp;quot;,pod=&amp;quot;latency-api-971504058-jzs5h&amp;quot;} 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为有两个 &lt;code&gt;kube-state-metrics&lt;/code&gt; 实例在运行，所以出现了两条结果。这两个指标可以用向量匹配的方式进行合并。他们的值是一致的，所以用 &lt;code&gt;min&lt;/code&gt;/&lt;code&gt;max&lt;/code&gt; 都可以。后面的内容会用 &lt;code&gt;label_app&lt;/code&gt; 进行聚合，所以需要保留这个指标标签。另外 &lt;code&gt;pod&lt;/code&gt; 标签也是需要保留的，用于进行连接。因为在 &lt;code&gt;kube_pod_labels&lt;/code&gt; 中，Pod 的指标标签是 &lt;code&gt;pod&lt;/code&gt;，而在 &lt;code&gt;containers_memory_usage_bytes&lt;/code&gt; 中则变成了 &lt;code&gt;pod_name&lt;/code&gt;。因此需要用一个 &lt;code&gt;label_replace&lt;/code&gt; 进行重命名：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;max by (pod_name,label_app) (
  label_replace(kube_pod_labels{label_app!=&amp;quot;&amp;quot;},&amp;quot;pod_name&amp;quot;,&amp;quot;$1&amp;quot;,&amp;quot;pod&amp;quot;,&amp;quot;(.*)&amp;quot;)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;返回内容大致如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;{label_app=&amp;quot;latency-api&amp;quot;,pod_name=&amp;quot;latency-api-971504058-n8k6d&amp;quot;}  1
{label_app=&amp;quot;latency-api&amp;quot;,pod_name=&amp;quot;latency-api-971504058-jzs5h&amp;quot;}  1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来就可以用向量匹配的方式来把 &lt;code&gt;container_memory_usage_bytes&lt;/code&gt; 和前面的表达式进行合并了。这里用到了 &lt;code&gt;*&lt;/code&gt;，他把内存用量乘以 &lt;code&gt;kube_pod_labels&lt;/code&gt; 里面的匹配值，然而这个值总是 1，所以其实没什么作用。&lt;/p&gt;

&lt;p&gt;每个 Pod 会有多个容器，也就是说可能有多个 &lt;code&gt;container_memory_usage_bytes&lt;/code&gt;，因此需要用到 &lt;code&gt;group_left&lt;/code&gt;。因为要保留 &lt;code&gt;label_app&lt;/code&gt; 这一指标标签，所以用它作为 &lt;code&gt;group_left&lt;/code&gt; 的参数。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;container_memory_usage_bytes * on (pod_name) group_left(label_app)
max by (pod_name,label_app) (
label_replace(kube_pod_labels{label_app!=&amp;quot;&amp;quot;},&amp;quot;pod_name&amp;quot;,&amp;quot;$1&amp;quot;,&amp;quot;pod&amp;quot;,&amp;quot;(.*)&amp;quot;)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用下面的表达式，可以聚合所有 Pod 的内存用量指标：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;sum by (label_app,namespace) (
  container_memory_usage_bytes * on (pod_name) group_left(label_app)
  max by (pod_name,label_app) (
    label_replace(kube_pod_labels{label_app!=&amp;quot;&amp;quot;},&amp;quot;pod_name&amp;quot;,&amp;quot;$1&amp;quot;,&amp;quot;pod&amp;quot;,&amp;quot;(.*)&amp;quot;)
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/mem_per_app.png&#34; alt=&#34;mem_per_app&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;用-pod-标签-对-cpu-和-io-指标进行聚合&#34;&gt;用 Pod 标签 对 CPU 和 IO 指标进行聚合&lt;/h2&gt;

&lt;p&gt;既然已经能把 &lt;code&gt;kube_pod_labels&lt;/code&gt; 和 cadvisor 连接起来，那么这个能力范围就不仅限于内存了。&lt;/p&gt;

&lt;h3 id=&#34;cpu&#34;&gt;CPU&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;sum by (label_app,namespace) (
  rate(container_cpu_usage_seconds_total[2m]) * on (pod_name) group_left(label_app)
  max by (pod_name,label_app) (
    label_replace(kube_pod_labels{label_app!=&amp;quot;&amp;quot;},&amp;quot;pod_name&amp;quot;,&amp;quot;$1&amp;quot;,&amp;quot;pod&amp;quot;,&amp;quot;(.*)&amp;quot;)
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/cpu_per_app.png&#34; alt=&#34;cpu per app&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;磁盘-io&#34;&gt;磁盘 IO&lt;/h3&gt;

&lt;p&gt;我曾经想要展示一些磁盘 IO 统计，很不幸，这个功能又出&lt;a href=&#34;https://github.com/kubernetes/kubernetes/issues/55397&#34; target=&#34;_blank&#34;&gt;问题&lt;/a&gt;了。&lt;/p&gt;

&lt;h3 id=&#34;网络&#34;&gt;网络&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;sum by (label_app,namespace) (
  rate(container_network_transmit_bytes_total[2m]) * on (pod_name) group_left(label_app)
  max by (pod_name,label_app) (
    label_replace(kube_pod_labels{label_app!=&amp;quot;&amp;quot;},&amp;quot;pod_name&amp;quot;,&amp;quot;$1&amp;quot;,&amp;quot;pod&amp;quot;,&amp;quot;(.*)&amp;quot;)
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/net_per_app.png&#34; alt=&#34;net_per_app&#34; /&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的用户和工作负载身份</title>
      <link>/post/user-and-workload-authentication-kubernetes/</link>
      <pubDate>Mon, 04 Jul 2022 21:53:52 +0800</pubDate>
      <guid>/post/user-and-workload-authentication-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://learnk8s.io/authentication-kubernetes&#34; target=&#34;_blank&#34;&gt;User and workload identities in Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;http://arthurchiao.art/about/&#34; target=&#34;_blank&#34;&gt;Arthur Chiao&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本文中我们会试着解释，在 Kubernetes API Server 上如何对用户和工作负载进行认证的问题。&lt;/p&gt;

&lt;p&gt;Kubernetes API Server 开放了 HTTP API 接口，让最终用户、集群组件以及外部组件可以进行通信。&lt;/p&gt;

&lt;p&gt;绝大多数操作都可以用 &lt;code&gt;kubectl&lt;/code&gt; 来完成，而且也可以使用 REST 调用的方式直接访问 API。&lt;/p&gt;

&lt;p&gt;但是如何只允许认证用户访问 API 呢？&lt;/p&gt;

&lt;h2 id=&#34;使用-curl-访问-kubernetes-api&#34;&gt;使用 &lt;code&gt;curl&lt;/code&gt; 访问 Kubernetes API&lt;/h2&gt;

&lt;p&gt;让我们从调用 Kubernetes API 开始。&lt;/p&gt;

&lt;p&gt;要列出集群中的所有命名空间，可以执行下列命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ export API_SERVER_URL=https://10.5.5.5:6443

$ curl $API_SERVER_URL/api/v1/namespaces
curl: (60) Peer Certificate issuer is not recognized.
# truncated output
If you&#39;d like to turn off curl&#39;s verification of the certificate, use the -k (or --insecure) option.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;输出内容表明，API Server 的接口用一个未识别的证书（例如自签发）提供了 &lt;code&gt;https&lt;/code&gt; 服务，所以 &lt;code&gt;curl&lt;/code&gt; 中断了这个请求。&lt;/p&gt;

&lt;p&gt;接下来我们用 &lt;code&gt;-k&lt;/code&gt; 参数跳过证书验证过程，并观察产生的响应：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;# curl -k $API_SERVER_URL/api/v1/namespaces
{
  &amp;quot;kind&amp;quot;: &amp;quot;Status&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;status&amp;quot;: &amp;quot;Failure&amp;quot;,
  &amp;quot;message&amp;quot;: &amp;quot;namespaces is forbidden: User \&amp;quot;system:anonymous\&amp;quot; cannot list resource \&amp;quot;namespaces\&amp;quot; ...&amp;quot;,
  &amp;quot;reason&amp;quot;: &amp;quot;Forbidden&amp;quot;,
  &amp;quot;details&amp;quot;: { &amp;quot;kind&amp;quot;: &amp;quot;namespaces&amp;quot; },
  &amp;quot;code&amp;quot;: 403
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在我们拿到了响应，但是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;对 API 端点的访问被禁止了（返回码 &lt;code&gt;403&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;用户身份被识别为 &lt;code&gt;system:anonymous&lt;/code&gt;，这个用户无权列出命名空间&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;上面的操作揭示了 &lt;code&gt;kube-apiserver&lt;/code&gt; 的部分工作机制：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先识别请求用户的身份&lt;/li&gt;
&lt;li&gt;然后决策这个用户是否有权完成操作&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;正式一点的说法分别叫认证（也叫 AuthN）和鉴权（也叫 AuthZ）：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;发起 &lt;code&gt;curl&lt;/code&gt; 请求时，流量触达 Kubernetes API Server&lt;/li&gt;
&lt;li&gt;在 API Server 里，认证模块会首先收到请求。如果认证失败，请求就会被标识为 &lt;code&gt;anonymous&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;认证之后就进入鉴权环节、 匿名访问没有权限，所以鉴权组件拒绝请求，并返回 &lt;code&gt;403&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;再次检视刚才的 &lt;code&gt;curl&lt;/code&gt; 请求：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;因为没有提供用户凭据，Kubernetes 认证模块会给请求标记为匿名请求&lt;/li&gt;
&lt;li&gt;根据 Kubernetes API Server 配置，可能会收到一个 &lt;a href=&#34;https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses/6937030#6937030&#34; target=&#34;_blank&#34;&gt;401 Unauthorized 代码&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Kubernetes 鉴权模块会检查 &lt;code&gt;system:anonymous&lt;/code&gt; 是否具有列出命名空间的权限，如果没有，就返回 &lt;code&gt;403 Forbidden&lt;/code&gt; 错误信息&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;例如 Kubelet 需要连接到 Kubernetes API 来报告状态：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/kubelet.png&#34; alt=&#34;kubelet&#34; /&gt;&lt;/p&gt;

&lt;p&gt;调用请求可能使用 Token、证书或者外部管理的认证来提供身份。认证模块是整个系统的第一个门槛。&lt;/p&gt;

&lt;p&gt;Kubernetes 的认证模块提供的几个重点能力：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;同时支持人和非人用户&lt;/li&gt;
&lt;li&gt;同时支持内部用户（Kubernetes 负责创建和管理的账号）和外部用户（例如集群外部署的应用）&lt;/li&gt;
&lt;li&gt;支持标准的认证策略，例如静态 Token、Bearer Token、X509 认证、OIDC 等&lt;/li&gt;
&lt;li&gt;同时支持多种认证策略&lt;/li&gt;
&lt;li&gt;可以加入或者移除认证策略&lt;/li&gt;
&lt;li&gt;还可以授权匿名用户访问 API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下面我们会走进观察认证模块的工作过程。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文聚焦于认证领域。要了解更多鉴权内容，可以阅读 &lt;a href=&#34;https://learnk8s.io/rbac-kubernetes&#34; target=&#34;_blank&#34;&gt;Limiting access to Kubernetes resources with RBAC&lt;/a&gt; 一文。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;kubernetes-api-的内外部用户区别&#34;&gt;Kubernetes API 的内外部用户区别&lt;/h2&gt;

&lt;p&gt;Kubernetes API 支持两种 API 用户：内部和外部。&lt;/p&gt;

&lt;p&gt;这两个东西有什么不同呢？&lt;/p&gt;

&lt;p&gt;如果用户是集群的内部用户，我们需要给它定义一个规范（例如数据模型）；而外部用户的规范是已经存在的。所以我们将用户分成下面几类：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes 管理的用户&lt;/strong&gt;：Kubernetes 创建，并由集群内应用使用的用户账号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非 Kubernetes 管理用户&lt;/strong&gt;：在 Kubernetes 集群外的用户，例如：

&lt;ul&gt;
&lt;li&gt;集群管理员发放的静态 Token 或证书&lt;/li&gt;
&lt;li&gt;使用 Keystone、Google Account 以及 LDAP 等进行认证的用户&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;授权外部用户访问集群&#34;&gt;授权外部用户访问集群&lt;/h2&gt;

&lt;p&gt;假设有如下场景：使用 Bearer token 访问 Kubernetes。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;curl --cacert ${CACERT} \
  --header &amp;quot;Authorization: Bearer &amp;lt;my token&amp;gt;&amp;quot; \
  -X GET ${APISERVER}/api
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubernetes API Server 是如何将 Token 识别为身份的？&lt;/p&gt;

&lt;p&gt;Kubernetes 并不管理外部用户，所以应该有一种机制来从外部资源中获取信息（例如用户名和用户组）。&lt;/p&gt;

&lt;p&gt;换句话说，Kubernetes API 接到了带有 Token 的请求后，就应该能够提取信息并进行后续的决策了。&lt;/p&gt;

&lt;p&gt;下面用例子来解释一下这个场景。&lt;/p&gt;

&lt;p&gt;创建一个 CSV 文件，其中包含了用户、Token 和用户组：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;token1,arthur,1,&amp;quot;admin,dev,qa&amp;quot;
token2,daniele,2,dev
token3,errge,3,qa
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;文件格式为 &lt;code&gt;token, user, uid, groups&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;用 &lt;code&gt;--token-auth-file&lt;/code&gt; 参数启动一个 minikube 集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ mkdir -p ~/.minikube/files/etc/ca-certificates
$ cd ~/.minikube/files/etc/ca-certificates
$ cat &amp;lt;&amp;lt; | tokens.csv
token1,arthur,1,&amp;quot;admin,dev,qa&amp;quot;
token2,daniele,2,dev
token3,errge,3,qa
EOF
$ minikube start \
  --extra-config=apiserver.token-auth-file=/etc/ca-certificates/tokens.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;为了发送请求给 Kubernetes API，需要集群的 IP 地址以及证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /Users/learnk8s/.minikube/ca.crt
    extensions:
    - extension:
        last-update: Fri, 10 Jun 2022 12:21:45 +08
        provider: minikube.sigs.k8s.io
        version: v1.25.2
      name: cluster_info
    server: https://127.0.0.1:57761
  name: minikube
# truncated output
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来向集群发送一个请求：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;$ export APISERVER=https://127.0.0.1:57761
$ export CACERT=/Users/learnk8s/.minikube/ca.crt
$ curl --cacert ${CACERT} -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;Status&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;metadata&amp;quot;: {},
  &amp;quot;status&amp;quot;: &amp;quot;Failure&amp;quot;,
  &amp;quot;message&amp;quot;: &amp;quot;forbidden: User \&amp;quot;system:anonymous\&amp;quot; cannot get path \&amp;quot;/\&amp;quot;&amp;quot;,
  &amp;quot;reason&amp;quot;: &amp;quot;Forbidden&amp;quot;,
  &amp;quot;details&amp;quot;: {},
  &amp;quot;code&amp;quot;: 403
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;响应信息表明，我们用匿名身份访问了 API，并且没有任何权限。&lt;/p&gt;

&lt;p&gt;接下来用 &lt;code&gt;token1&lt;/code&gt;（来自于 &lt;code&gt;tokens.csv&lt;/code&gt; 文件中的用户 &lt;code&gt;arthur&lt;/code&gt;）发起请求：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;$ export APISERVER=https://127.0.0.1:57761
$ export CACERT=/Users/learnk8s/.minikube/ca.crt
$ curl --cacert ${CACERT} --header &amp;quot;Authorization: Bearer token1&amp;quot; -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;Status&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;metadata&amp;quot;: {},
  &amp;quot;status&amp;quot;: &amp;quot;Failure&amp;quot;,
  &amp;quot;message&amp;quot;: &amp;quot;forbidden: User \&amp;quot;arthur\&amp;quot; cannot get path \&amp;quot;/\&amp;quot;&amp;quot;,
  &amp;quot;reason&amp;quot;: &amp;quot;Forbidden&amp;quot;,
  &amp;quot;details&amp;quot;: {},
  &amp;quot;code&amp;quot;: 403
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如上所见，Kubernetes 能够识别出请求来自于 &lt;code&gt;Arthur&lt;/code&gt;。发生了什么呢？&lt;code&gt;tokens.csv&lt;/code&gt; 和 &lt;code&gt;--token-auth-file&lt;/code&gt; 参数起了什么作用？&lt;strong&gt;Kubernetes 有多种认证插件，现在我们使用的是静态 Token 文件&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;重放一下刚才的过程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;API Server 启动后，读取 CSV 文件，把用户数据保存在内存里&lt;/li&gt;
&lt;li&gt;用 Token 向 API Server 发起请求&lt;/li&gt;
&lt;li&gt;API Server 用 Token 找到匹配的用户，并解出剩余的用户信息（例如用户、用户组等）&lt;/li&gt;
&lt;li&gt;这些详细信息会被包含在请求中，传递给鉴权模块&lt;/li&gt;
&lt;li&gt;当前的鉴权模块（例如 RBAC）找不到 Arthur 的权限，拒绝请求。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;创建一个 &lt;code&gt;ClusterRoleBinding&lt;/code&gt; 就能快速修复这个问题：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin
subjects:
- kind: User
  name: arthur
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用下面的命令把对象提交给集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f admin-binding.yaml
clusterrolebinding.rbac.authorization.k8s.io/admin created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次执行命令就会成功了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;curl --cacert ${CACERT} \
  --header &amp;quot;Authorization: Bearer token1&amp;quot; \
  -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;APIVersions&amp;quot;,
  &amp;quot;versions&amp;quot;: [
    &amp;quot;v1&amp;quot;
  ],
  &amp;quot;serverAddressByClientCIDRs&amp;quot;: [
    {
      &amp;quot;clientCIDR&amp;quot;: &amp;quot;0.0.0.0/0&amp;quot;,
      &amp;quot;serverAddress&amp;quot;: &amp;quot;192.168.49.2:8443&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面向 &lt;code&gt;kube-apiserver&lt;/code&gt; 发送了一个 HTTP 请求，认证模块会尝试&lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/authentication/&#34; target=&#34;_blank&#34;&gt;将如下属性附加到请求之中&lt;/a&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Username&lt;/code&gt;：字符串，例如 &lt;code&gt;kube-admin&lt;/code&gt;、&lt;code&gt;jane@example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UID&lt;/code&gt;：字符串，相对用户名来说，UID 是一个更稳定的属性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Groups&lt;/code&gt;：例如 &lt;code&gt;system:masters&lt;/code&gt;、&lt;code&gt;devops-team&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;附加字段：可能对认证过程有帮助的一些其他字段&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;请求上下文中加入这些信息之后，后续的 Kubernetes API 组件都能读取这些信息，这些信息对认证插件来说是透明的。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;可以使用 Token 向集群发起一个认证请求&lt;/li&gt;
&lt;li&gt;Kubernetes 把请求 Token 进行匹配。这是一个外部用户，因此需要依赖一个外部的用户管理系统（这里指的就是那个 CSV 文件）&lt;/li&gt;
&lt;li&gt;拿到用户名、ID、用户组等信息之后，这些信息会被传递给鉴权模块进行校验&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;前面的例子中为用户名创建了一个 &lt;code&gt;ClusterRoleBinding&lt;/code&gt;。其实 CSV 中为 &lt;code&gt;Arthur&lt;/code&gt; 设置了三个用户组（&lt;code&gt;admin&lt;/code&gt;、&lt;code&gt;dev&lt;/code&gt;、&lt;code&gt;qa&lt;/code&gt;），因此也可以写成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin
subjects:
- kind: Group
  name: admin
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;静态 Token 是一种简易的认证机制，集群管理员可以随意生成 Token 并指派给用户。但是这种方式有一定弊端：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;必须知道所有的用户&lt;/li&gt;
&lt;li&gt;编辑 &lt;code&gt;tokens.csv&lt;/code&gt; 文件需要重启 API Server&lt;/li&gt;
&lt;li&gt;Token 不会过期&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kubernetes 还提供了其它几种外部认证机制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;X.509 客户端证书&lt;/li&gt;
&lt;li&gt;OpenID&lt;/li&gt;
&lt;li&gt;认证代理&lt;/li&gt;
&lt;li&gt;Webhook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;每种方式都有各自的利弊，但是所有的工作流都跟静态 Token 类似：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;身份被保存在集群之外&lt;/li&gt;
&lt;li&gt;用户使用 Token 向 API Server 发起请求&lt;/li&gt;
&lt;li&gt;Kubernetes 向外部认证源（例如 CSV 文件、认证服务、LDAP 等）请求检查 Token 的有效性&lt;/li&gt;
&lt;li&gt;如果认证有效，Kubernetes 会拿到用户名和其他元数据&lt;/li&gt;
&lt;li&gt;鉴权策略会使用这些数据来判断用户是否具备访问该资源的权限&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;那么如何选择认证插件呢？实际上可以同时启用多个认证插件，Kubernetes 会逐个调用每个插件，直到成功为止。&lt;/p&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;现在已经了解了外部用户的问题，接下来看看 Kubernetes 如何管理内部用户。&lt;/p&gt;

&lt;h2 id=&#34;用-serviceaccount-管理-kubernetes-内部认证&#34;&gt;用 ServiceAccount 管理 Kubernetes 内部认证&lt;/h2&gt;

&lt;p&gt;在 Kubernetes 中，内部用户使用 Service Account 的概念来表达。&lt;/p&gt;

&lt;p&gt;这些身份通过 &lt;code&gt;kube-apiserver&lt;/code&gt; 创建，并分配给应用。&lt;/p&gt;

&lt;p&gt;Service Account 会有相关联的 Token，应用向 &lt;code&gt;kube-apiserver&lt;/code&gt; 发起请求时，会共享这个 Token 用于认证。&lt;/p&gt;

&lt;p&gt;观察一下 Service Account 的定义：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create serviceaccount test
serviceaccount/test created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个资源的具体内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get serviceaccount test -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test
secrets:
- name: test-token-6tmx7
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果集群版本高于 &lt;code&gt;1.24&lt;/code&gt;，输出会有不同：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get serviceaccount test -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;差距很明显，只有老版本集群中会有 &lt;code&gt;secrets&lt;/code&gt; 字段。&lt;/p&gt;

&lt;p&gt;这个 Secret 包含了必要的 Token，API Server 可以用 Token 对请求进行认证：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get secret test-token-6tmx7
apiVersion: v1
kind: Secret
metadata:
  name: test-token-6tmx7
type: kubernetes.io/service-account-token
data:
  ca.crt: LS0tLS1CR…
  namespace: ZGVmYXVs…
  token: ZXlKaGJHY2…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面的 YAML 代码把这个身份分配给 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  serviceAccount: test
  containers:
  - image: nginx
    name: nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交到集群，创建 Pod 并进入他的 Bash：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f nginx.yaml
pod/nginx created
$ kubectl exec -ti nginx -- bash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;发起请求：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;$ export APISERVER=https://kubernetes.default.svc
$ export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
$ export CACERT=${SERVICEACCOUNT}/ca.crt
$ export TOKEN=&amp;quot;token here&amp;quot;
$ curl --cacert ${CACERT} --header &amp;quot;Authorization: Bearer ${TOKEN}&amp;quot; -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;APIVersions&amp;quot;,
  &amp;quot;versions&amp;quot;: [
    &amp;quot;v1&amp;quot;
  ],
  &amp;quot;serverAddressByClientCIDRs&amp;quot;: [
    {
      &amp;quot;clientCIDR&amp;quot;: &amp;quot;0.0.0.0/0&amp;quot;,
      &amp;quot;serverAddress&amp;quot;: &amp;quot;192.168.49.2:8443&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;调用成功了。&lt;/p&gt;

&lt;p&gt;Kubernetes 1.24 以后的版本不再创建 Secret，那怎么获取 Token 呢？&lt;/p&gt;

&lt;h2 id=&#34;为-service-account-生成临时认证&#34;&gt;为 Service Account 生成临时认证&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;新版本的 Kubernetes 中，Kubelet 负责从 API Server 申请临时 Token。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Token 格式类似 Secret 对象中的 Token，但是有个很大的不同是——他会过期。&lt;/p&gt;

&lt;p&gt;这个 Token 不会被注入到 Secret 里面，而是使用 &lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/projected-volumes/&#34; target=&#34;_blank&#34;&gt;Projected Volume&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;在 Kubernetes 1.24 中重复一下刚才的测试。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create serviceaccount test
serviceaccount/test created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建一个 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  serviceAccount: test
  containers:
  - name: nginx
    image: nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把 Pod 提交到集群上：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f nginx.yaml
pod/nginx created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;首先确认一下，集群里没有 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get secrets
No resources found in default namespace.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后进入 Pod Shell：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl exec -ti nginx -- bash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查一下 Token 的加载情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;$ export APISERVER=https://kubernetes.default.svc
$ export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
$ export CACERT=${SERVICEACCOUNT}/ca.crt
$  export TOKEN=$(cat ${SERVICEACCOUNT}/token)
$ curl --cacert ${CACERT} --header &amp;quot;Authorization: Bearer ${TOKEN}&amp;quot; -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;APIVersions&amp;quot;,
  &amp;quot;versions&amp;quot;: [
    &amp;quot;v1&amp;quot;
  ],
  &amp;quot;serverAddressByClientCIDRs&amp;quot;: [
    {
      &amp;quot;clientCIDR&amp;quot;: &amp;quot;0.0.0.0/0&amp;quot;,
      &amp;quot;serverAddress&amp;quot;: &amp;quot;192.168.49.2:8443&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;还是能成功，这个 Token 是怎么加载的？我们来看一下 Pod 的定义：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get pod nginx -o yaml
apiVersion: v1
kind: Pod
  name: nginx
spec:
  containers:
  - image: nginx
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-69mqr
      readOnly: true
  serviceAccount: test
  volumes:
  - name: kube-api-access-69mqr
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;内容有点多，解析一下。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;这里声明了一个 &lt;code&gt;kube-api-access-69mqr&lt;/code&gt; 卷&lt;/li&gt;
&lt;li&gt;这个卷用只读的方式加载到了 &lt;code&gt;/var/run/secrets/kubernetes.io/serviceaccount&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个卷用的是 &lt;code&gt;projected&lt;/code&gt; 类型。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Projected 卷能把多个卷聚合在一起。但并不是所有类型的卷都能够绑定到 Projected 卷里面，目前仅限于 &lt;code&gt;downwardAPI&lt;/code&gt;、&lt;code&gt;configMap&lt;/code&gt; 以及 &lt;code&gt;serviceAccountToken&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在这个例子里，Projected 卷的组成成分包括：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;serviceAccountToken&lt;/code&gt; 卷被加载到 &lt;code&gt;token&lt;/code&gt; 路径&lt;/li&gt;
&lt;li&gt;&lt;code&gt;configMap&lt;/code&gt; 卷&lt;/li&gt;
&lt;li&gt;&lt;code&gt;downwardAPI&lt;/code&gt; 卷被加载到 &lt;code&gt;namespace&lt;/code&gt; 路径&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这些卷都是干嘛的？&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/projected-volumes/#serviceaccounttoken&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;serviceAccountToken&lt;/code&gt;&lt;/a&gt; 是一种特别的卷，从当前的 Service Account 中加载 Secret，并填充到 &lt;code&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;/code&gt; 文件中。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ConfigMap&lt;/code&gt; 卷会把 &lt;code&gt;ConfigMap&lt;/code&gt; 中的每个 Key 加载成目录里面的文件。&lt;/p&gt;

&lt;p&gt;这个文件的的内容就是对应 Key 的 Value（如果键值对的内容是 &lt;code&gt;replicas:1&lt;/code&gt;，就会表达为一个命名为 &lt;code&gt;replicas&lt;/code&gt; 的文件，其内容是 &lt;code&gt;1&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;本例中，ConfigMap 卷中加载了调用 API 所必须的 &lt;code&gt;ca.crt&lt;/code&gt; 证书。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/volumes/#downwardapi&#34; target=&#34;_blank&#34;&gt;downwardAPI&lt;/a&gt; 卷是一种特殊类型，使用 &lt;a href=&#34;https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#the-downward-api&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;downwardAPI&lt;/code&gt;&lt;/a&gt;，将 Pod 信息开放给容器。&lt;/p&gt;

&lt;p&gt;在这个例子里，用这种方法将当前命名空间用文件的方式暴露给容器。&lt;/p&gt;

&lt;p&gt;可以在 Pod 里验证一下这个能力：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
$ export NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
$ echo $NAMESPACE
default
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;知道了 Token 的加载方式之后，那为什么 Kubernetes 要放弃 Secret 改用这种方式呢？&lt;/p&gt;

&lt;p&gt;主要原因是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Secret 中的 Token 永不过期&lt;/li&gt;
&lt;li&gt;创建 Service Account 的时候，会异步创建一个带令牌的 Secret&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;但是如果你只需要 Token，却不需要 Pod 呢？是否可以不加载 Projected Volume 就拿到 Token 数据呢？kubectl 有个新命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create token test
eyJhbGciOiJSUzI1NiIsImtpZCI6ImctMHJNO…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个 Token 是临时的，和 Kubelet 加载到 Pod 里面的 Token 是一样的。&lt;/p&gt;

&lt;p&gt;重复执行命令会看到不同的结果，那么这个 Token 只是个长字符串吗？&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Projected Servivce Account Token 是个签了名的 JWT Token&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以把这个字符串复制到 &lt;a href=&#34;https://jwt.io&#34; target=&#34;_blank&#34;&gt;jwt.io&lt;/a&gt; 网站上，处理之后的输出内容结构如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Header 描述了 Token 的签名方式&lt;/li&gt;
&lt;li&gt;Payload 就是 Token 中的真实数据&lt;/li&gt;
&lt;li&gt;Signature 用于校验 Token 是否被修改&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&#34;images/token.png&#34; alt=&#34;token&#34; /&gt;&lt;/p&gt;

&lt;p&gt;观察一下这个 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;aud&amp;quot;: [
    &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;
  ],
  &amp;quot;exp&amp;quot;: 1655083796,
  &amp;quot;iat&amp;quot;: 1655080196,
  &amp;quot;iss&amp;quot;: &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;,
  &amp;quot;kubernetes.io&amp;quot;: {
    &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;,
    &amp;quot;serviceaccount&amp;quot;: {
      &amp;quot;name&amp;quot;: &amp;quot;test&amp;quot;,
      &amp;quot;uid&amp;quot;: &amp;quot;6af2abe9-d8d8-4b8a-9bb5-3cc96442b322&amp;quot;
    }
  },
  &amp;quot;nbf&amp;quot;: 1655080196,
  &amp;quot;sub&amp;quot;: &amp;quot;system:serviceaccount:default:test&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的字段值得讨论：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2&#34; target=&#34;_blank&#34;&gt;sub&lt;/a&gt;：主体。本例中的主体是存在于缺省命名空间中的名为 &lt;code&gt;test&lt;/code&gt; 的 Service Account。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3&#34; target=&#34;_blank&#34;&gt;aud&lt;/a&gt;：受众。这个 Token 对当前 Kubernetes 集群生效。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1&#34; target=&#34;_blank&#34;&gt;iss&lt;/a&gt;：签发者。因为这个 Token 是当前 Kubernetes 签发的，所以取值为当前集群的域名。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubernetes.io&lt;/code&gt;：自定义字段，用于描述 Kubernetes 的细节。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;从 Nginx Pod 中读取 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;aud&amp;quot;: [
    &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;
  ],
  &amp;quot;exp&amp;quot;: 1686617744,
  &amp;quot;iat&amp;quot;: 1655081744,
  &amp;quot;iss&amp;quot;: &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;,
  &amp;quot;kubernetes.io&amp;quot;: {
    &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;,
    &amp;quot;pod&amp;quot;: {
      &amp;quot;name&amp;quot;: &amp;quot;nginx&amp;quot;,
      &amp;quot;uid&amp;quot;: &amp;quot;a11defcb-f510-4d49-9c4f-2e8e8da1c33c&amp;quot;
    },
    &amp;quot;serviceaccount&amp;quot;: {
      &amp;quot;name&amp;quot;: &amp;quot;test&amp;quot;,
      &amp;quot;uid&amp;quot;: &amp;quot;6af2abe9-d8d8-4b8a-9bb5-3cc96442b322&amp;quot;
    },
    &amp;quot;warnafter&amp;quot;: 1655085351
  },
  &amp;quot;nbf&amp;quot;: 1655081744,
  &amp;quot;sub&amp;quot;: &amp;quot;system:serviceaccount:default:test&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Payload 中包含了 Pod 的名字和 UUID。但是这些信息是谁在消费呢？&lt;/p&gt;

&lt;p&gt;不仅能够检查 Token 的完整性和有效性，甚至还可以区分出同一个 Deployment 中的两个 Pod 的区别。&lt;/p&gt;

&lt;p&gt;这个功能很有用，原因是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;授权粒度精细到特定 Pod&lt;/li&gt;
&lt;li&gt;特定身份被攻破，也只会影响单一单元&lt;/li&gt;
&lt;li&gt;从一个 API 调用就能够知道其中包含的命名空间和 Pod&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;aws-如何将-iam-集成到-kubernetes&#34;&gt;AWS 如何将 IaM 集成到 Kubernetes&lt;/h2&gt;

&lt;p&gt;设想一个场景，在 AWS 中运行 Kubernetes 集群之中，并希望从集群中上传文件到 S3 的场景。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意在 &lt;a href=&#34;https://azure.github.io/azure-workload-identity/docs/&#34; target=&#34;_blank&#34;&gt;Azure&lt;/a&gt; 和 &lt;a href=&#34;https://cloud.google.com/config-connector/docs/overview&#34; target=&#34;_blank&#34;&gt;GCP&lt;/a&gt; 也存在同等能力。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;通常来说，需要用一个角色来完成这一任务，但是 AWS 的 IAM 角色只能赋予给计算实例、而非 Pod，换句话说，AWS 对 Pod 并无认知。&lt;/p&gt;

&lt;p&gt;2019 年底，AWS 提供了一种原生的 Kubernetes 集成 IAM 的机制，被称为 &lt;a href=&#34;https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html&#34; target=&#34;_blank&#34;&gt;IAM Roles for Service Accounts (IRSA)&lt;/a&gt;，IRSA 在身份和 Projected Service Account Token 之间建立了联系。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;创建一个 IAM 策略，其中包含了允许访问的资源&lt;/li&gt;
&lt;li&gt;创建一个角色，其中包含了上一步中的策略，记录其 &lt;code&gt;ARN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;创建一个 Projected Service Account Token，并用文件的方式进行加载&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;把 Role ARN 和 Projected Service Account Token 呈现在 Pod 的环境变量之中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Pod
metadata:
  name: myapp
spec:
  serviceAccountName: my-serviceaccount
  containers:
  - name: myapp
    image: myapp:1.2
    env:
    - name: AWS_ROLE_ARN
      value: arn:aws:iam::111122223333:policy/my-role
    - name: AWS_WEB_IDENTITY_TOKEN_FILE
      value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    volumeMounts:
    - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
      name: aws-iam-token
      readOnly: true
  volumes:
  - name: aws-iam-token
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          audience: sts.amazonaws.com
          expirationSeconds: 86400
          path: token
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有了这一配置，就能向 S3 上传文件了。&lt;/p&gt;

&lt;p&gt;应用会使用这两个环境变量作为连接到 S3 所需要的 Token，但是如何实现的呢？&lt;/p&gt;

&lt;p&gt;是 Kubernetes 而非 AWS 生成了 Token，那么 AWS 如何知道 Token 的有效性呢——是的 AWS 不知道。&lt;/p&gt;

&lt;p&gt;AWS SDK 使用角色 ARN 以及 Projected Service Account Token 来交换标准的 AWS 访问凭据。&lt;/p&gt;

&lt;p&gt;如果不用 AWS SDK 又怎么办呢？应用程序向 AWS IAM 发起请求，&lt;a href=&#34;https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role-with-web-identity.html&#34; target=&#34;_blank&#34;&gt;为当前身份（Service Account）换取一个角色&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;IAM 收到这个 Token 后，会进行解压并检查 iss 字段，来判断 JWT Token 的合法性。&lt;/p&gt;

&lt;p&gt;这个字段通常会被配置为用于创建该 Token 的公钥。&lt;/p&gt;

&lt;p&gt;前面说过，这个 URL 指向 Kubernetes 集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;{
  &amp;quot;aud&amp;quot;: [
    &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;
  ],
  &amp;quot;exp&amp;quot;: 1686617744,
  &amp;quot;iat&amp;quot;: 1655081744,
  &amp;quot;iss&amp;quot;: &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;,
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;注意，需要把这个 URL 改成一个完全限定名（FQDN），否则 AWS IAM 无法触达。&lt;a href=&#34;https://azure.github.io/azure-workload-identity/docs/installation/self-managed-clusters/configurations.html&#34; target=&#34;_blank&#34;&gt;可以用 &lt;code&gt;--service-account-issuer&lt;/code&gt; 参数来指定&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这个 URL 是一个标准的 OIDC Provider，AWS IAM 会查看两个路径：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{Issuer URL}/.well-known/openid-configuration&lt;/code&gt;：又被称为 OIDC 发现文档。其中包含了签发者的配置元数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{Issuer URL}/openid/v1/jwks&lt;/code&gt;：其中包含了签名公钥，用于验证 Service Account Token 的真实性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要注意，缺省情况下，这两个端点是不会暴露的，需要集群管理员进行设计。&lt;/p&gt;

&lt;p&gt;首先看看 JWKS 端点：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;curl {Issuer URL}/openid/v1/jwks
  &amp;quot;keys&amp;quot;: [
    {
      &amp;quot;use&amp;quot;: &amp;quot;sig&amp;quot;,
      &amp;quot;kty&amp;quot;: &amp;quot;RSA&amp;quot;,
      &amp;quot;kid&amp;quot;: &amp;quot;ZO4TUgVjBzMWKVP8mmBwKLvsuyn8z-gfqUp27q9lO4w&amp;quot;,
      &amp;quot;alg&amp;quot;: &amp;quot;RS256&amp;quot;,
      &amp;quot;n&amp;quot;: &amp;quot;34a81xuMe…&amp;quot;,
      &amp;quot;e&amp;quot;: &amp;quot;AQAB&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;AWS IAM 会收到公钥，并校验 Token。&lt;a href=&#34;https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html&#34; target=&#34;_blank&#34;&gt;下面的代码&lt;/a&gt;用于校验：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-js&#34;&gt;var jwt = require(&#39;jsonwebtoken&#39;)
var jwkToPem = require(&#39;jwk-to-pem&#39;)
var pem = jwkToPem(jwk /* &amp;quot;kid&amp;quot; value from the jkws file */)
jwt.verify(token /* this is the token to verify */, pem, { algorithms: [&#39;RS256&#39;] }, function(err, decodedToken) {
  // rest of the code
})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果 Token 有效，就生成一个具备指定权限的 Access Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;Credentials&amp;quot;: {
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;ASIAWY4CVPOBS4OIBWNL&amp;quot;,
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;02n52u8Smc76…&amp;quot;,
        &amp;quot;SessionToken&amp;quot;: &amp;quot;IQoJb3JpZ…&amp;quot;,
        &amp;quot;Expiration&amp;quot;: &amp;quot;2022-06-13T10:50:25+00:00&amp;quot;
    },
    &amp;quot;SubjectFromWebIdentityToken&amp;quot;: &amp;quot;system:serviceaccount:default:test&amp;quot;,
    &amp;quot;AssumedRoleUser&amp;quot;: {
        &amp;quot;AssumedRoleId&amp;quot;: &amp;quot;AROAWY4CVPOBXUSBA5C2B:test&amp;quot;,
        &amp;quot;Arn&amp;quot;: &amp;quot;arn:aws:sts::[aws account id]:assumed-role/oidc/test&amp;quot;
    },
    &amp;quot;Provider&amp;quot;: &amp;quot;arn:aws:iam::[aws account id]:oidc-provider/[bucket name].s3.amazonaws.com&amp;quot;,
    &amp;quot;Audience&amp;quot;: &amp;quot;test&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;拿到新凭据后，就可以用来访问 S3 存储桶了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/exchange.png&#34; alt=&#34;exchange&#34; /&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Projected Serivce Account Token 代表一个集群内有效的身份它可以用来交换到一个其他场景下有效的 Token&lt;/li&gt;
&lt;li&gt;AWS IaM 服务收到这个 Token，并读取其 &lt;code&gt;iss&lt;/code&gt; 字段的内容，用于验证 Token&lt;/li&gt;
&lt;li&gt;如果身份有效，就签发自己的 Token&lt;/li&gt;
&lt;li&gt;可以使用新的 Token 访问 AWS 的服务&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;另外还有一篇文章，完整的描述了&lt;a href=&#34;https://dev.to/danielepolencic/binding-aws-iam-roles-to-kubernetes-service-account-for-on-prem-clusters-1icc&#34; target=&#34;_blank&#34;&gt;手工进行集成的过程&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;这种方式可以用于访问外部资源，然而访问内部服务时，是否也需要这样操作呢?&lt;/p&gt;

&lt;h2 id=&#34;使用-token-review-api-校验-projected-service-account&#34;&gt;使用 Token Review API 校验 Projected Service Account&lt;/h2&gt;

&lt;p&gt;可以用 &lt;a href=&#34;https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/&#34; target=&#34;_blank&#34;&gt;Token Review API&lt;/a&gt; 来对集群创建的 Token 进行校验。&lt;/p&gt;

&lt;p&gt;首先为 Service Account 创建一个 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create token test
eyJhbG…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建 YAML 资源，并在其中包含 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: TokenReview
apiVersion: authentication.k8s.io/v1
metadata:
  name: test
spec:
  token: eyJhbG… # &amp;lt;- token
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交资源，注意 &lt;code&gt;-o yaml&lt;/code&gt; 输出的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl apply -o yaml -f token.yaml
apiVersion: authentication.k8s.io/v1
kind: TokenReview
metadata:
  name: test
spec:
  token: eyJhbG…
status:
  audiences:
    - https://kubernetes.default.svc.cluster.local
  authenticated: true
  user:
    groups:
      - system:serviceaccounts
      - system:serviceaccounts:default
      - system:authenticated
    uid: eccac137-25e2-4e84-9d83-18b2f9c5e5af
    username: system:serviceaccount:default:test
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Token Review API 的工作内容和 AWS IAM 集成类似：校验身份，并从 Token 中获取细节。当然，单一的 API 调用比 OIDC 流程要简单直接得多。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://learnk8s.io/microservices-authentication-kubernetes&#34; target=&#34;_blank&#34;&gt;还可以使用定制 Audience 的方式来限制访问范围&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;用-kubernetes-1-24-或者更高版本生成-service-account-的-secret&#34;&gt;用 Kubernetes 1.24 或者更高版本生成 Service Account 的 Secret&lt;/h2&gt;

&lt;p&gt;从 1.24 开始，Kubernetes 不再为 ServiceAccount 自动生成 Secret。然而你还是可以使用传统的方式来创建 Service Account 并用注解的方式来附加给一个 Secret。&lt;/p&gt;

&lt;p&gt;例如当前的 Service Account &lt;code&gt;test&lt;/code&gt; 中没有 &lt;code&gt;secret&lt;/code&gt; 对象。但是可以创建用这种方式创建 &lt;code&gt;Secret&lt;/code&gt; （和 &lt;code&gt;token&lt;/code&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: test
  annotations:
    kubernetes.io/service-account.name: &amp;quot;test&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交给集群之后，进行观察：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl describe secret test

Name:         test
Namespace:    default

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1111 bytes
namespace:  7 bytes
token:      eyJhbG…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;还可以用 Token Review API 来校验这个 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: TokenReview
apiVersion: authentication.k8s.io/v1
metadata:
  name: test
spec:
  token: eyJhbG…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交对象，并加入 &lt;code&gt;-o yaml&lt;/code&gt; 开关：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl apply -o yaml -f token.yaml

apiVersion: authentication.k8s.io/v1
kind: TokenReview
metadata:
  name: test
spec:
  token: eyJhbG…
status:
  audiences:
  - https://kubernetes.default.svc.cluster.local
  authenticated: true
  user:
    groups:
    - system:serviceaccounts
    - system:serviceaccounts:default
    - system:authenticated
    uid: eccac137-25e2-4e84-9d83-18b2f9c5e5af
    username: system:serviceaccount:default:test
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果把 Token 内容提交给 &lt;a href=&#34;https://jwt.io/&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;jwt.io&lt;/code&gt;&lt;/a&gt;，会发现 Token 没有过期时间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;
{
  &amp;quot;iss&amp;quot;: &amp;quot;kubernetes/serviceaccount&amp;quot;,
  &amp;quot;kubernetes.io/serviceaccount/namespace&amp;quot;: &amp;quot;default&amp;quot;,
  &amp;quot;kubernetes.io/serviceaccount/secret.name&amp;quot;: &amp;quot;test&amp;quot;,
  &amp;quot;kubernetes.io/serviceaccount/service-account.name&amp;quot;: &amp;quot;test&amp;quot;,
  &amp;quot;kubernetes.io/serviceaccount/service-account.uid&amp;quot;: &amp;quot;eccac137-25e2-4e84-9d83-18b2f9c5e5af&amp;quot;,
  &amp;quot;sub&amp;quot;: &amp;quot;system:serviceaccount:default:test&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这种情况和 Kubernetes 的传统行为是一致的。&lt;/p&gt;

&lt;h2 id=&#34;认证插件的选择&#34;&gt;认证插件的选择&lt;/h2&gt;

&lt;p&gt;Kubernetes 提供了以下的认证插件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;静态 Token 文件&lt;/li&gt;
&lt;li&gt;X.509 证书&lt;/li&gt;
&lt;li&gt;Open ID Connect&lt;/li&gt;
&lt;li&gt;Authentication proxy&lt;/li&gt;
&lt;li&gt;Webhook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如何选择呢？&lt;/p&gt;

&lt;p&gt;在前面一节里，我们讨论了静态 Token 文件的限制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;需要知道用户名&lt;/li&gt;
&lt;li&gt;修改 CSV 文件需要重启 API Server 才能生效&lt;/li&gt;
&lt;li&gt;Token 不会过期&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此静态 Token 文件不是生产环境中的最佳选择。&lt;/p&gt;

&lt;p&gt;X.509 客户端证书方案会略微好一些。&lt;/p&gt;

&lt;p&gt;使用 X.509 客户端证书认证：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;kube-apiserver&lt;/code&gt; 使用 &lt;code&gt;--client-ca-file=FILE&lt;/code&gt; 参数来指定 CA&lt;/li&gt;
&lt;li&gt;管理员为外部用户签发客户端证书。这些 X.509 客户端证书是自包含的，其中包含了用户名和用户组&lt;/li&gt;
&lt;li&gt;用户使用这个证书，用 TLS 方式发起对 API Server 的访问&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kube-apiserver&lt;/code&gt; 用 CA 证书对客户端证书进行认证，如果有效，则解析其中包含的用户名和用户组。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;工作流和静态 Token 类似，但还是有些区别：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;证书可以设置有效期&lt;/li&gt;
&lt;li&gt;创建新的客户端证书，无需修改 API Server 参数&lt;/li&gt;
&lt;li&gt;没有 CSV 文件，证书用 CRD 定义的方式来管理&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;然而，&lt;a href=&#34;https://www.tremolosecurity.com/post/kubernetes-dont-use-certificates-for-authentication&#34; target=&#34;_blank&#34;&gt;X.509 客户端证书也并不是一个值得推荐的方案&lt;/a&gt;。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;X.509 客户端证书通常是很长寿（以年计）&lt;/li&gt;
&lt;li&gt;CA 基础设施提供了作废证书的途径，但是 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/issues/18982&#34; target=&#34;_blank&#34;&gt;Kubernetes 不支持过期证书的检查&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;客户端证书是自包含的，因此用 RBAC 进行分组非常难&lt;/li&gt;
&lt;li&gt;为了对客户进行认证，必须点对点的连接 API Server，不能使用反向代理或者 WAF 防火墙。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;（临时）没有其它机制可用的应急场景下，正适合使用 X.509 认证方法。&lt;/p&gt;

&lt;p&gt;Kubeadm 和 OpenShift 缺省会设置 API Server 的证书认证能力，这样本地的 Kubectl 就可以使用了。&lt;/p&gt;

&lt;p&gt;除了上面的特例之外，可能最好的方式就是 OIDC 认证了。如果已经有了用于管理用户的 OpenID Connect 的基础设施，那就尤其合适了。这种情况下，可以用管理普通用户的方式来管理 Kubernetes 中的用户。&lt;/p&gt;

&lt;p&gt;OpenID Connect Provider 能够签发 &lt;a href=&#34;https://jwt.io/&#34; target=&#34;_blank&#34;&gt;JSON Web Token（JWT）&lt;/a&gt;，这意味着 Token 能够自动认证，无需连接到 Token 的签发方，并且会过期。&lt;/p&gt;

&lt;p&gt;最后两种认证插件是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;认证代理&lt;/li&gt;
&lt;li&gt;Webhook&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy&#34; target=&#34;_blank&#34;&gt;认证代理&lt;/a&gt; 插件能够通过外部的认证代理进行透明的认证。&lt;/p&gt;

&lt;p&gt;当用户向 Kubernetes 集群发起请求时，请求首先会被认证代理进行处理。这种认证插件可以编写自己的认证逻辑，因此用来实现其它插件不支持的认证方式是很合适的。&lt;/p&gt;

&lt;p&gt;最后 &lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication&#34; target=&#34;_blank&#34;&gt;Webhook Token&lt;/a&gt; 认证插件让用户能够用 &lt;a href=&#34;https://swagger.io/docs/specification/authentication/bearer-authentication/&#34; target=&#34;_blank&#34;&gt;HTTP Bearer Token&lt;/a&gt; 的方式，对 Kubernetes 请求进行自定义认证逻辑。&lt;/p&gt;

&lt;p&gt;Webhook Token 认证插件也同样适用于没有其它机制可用的场景。&lt;/p&gt;

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

&lt;p&gt;本文中阐述了 Kubernetes API Server 认证用户的能力。内容大致包括&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;外部用户和内部用户的区别&lt;/li&gt;
&lt;li&gt;Kubernetes API Server 如何实现不同的用户认证方法，例如静态 Token、Bearer Token、X.509 证书、OIDC 等&lt;/li&gt;
&lt;li&gt;Kubernetes 如何使用 Service Account 为内部用户授予身份&lt;/li&gt;
&lt;li&gt;使用 Secret 创建的 Token，和 Kubelet 创建的 Token 有什么区别&lt;/li&gt;
&lt;li&gt;Projected Volume 把多个卷聚合到一起的方法&lt;/li&gt;
&lt;li&gt;如何用 JWT 工具查看 Service Account Token&lt;/li&gt;
&lt;li&gt;和 OIDC 联邦，并且和 AWS 之类的云供应商进行集成的方式&lt;/li&gt;
&lt;li&gt;如何使用 API Review API 来校验 Service Account Token 的有效性。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;认证通过后，就进入鉴权环节了。然后可以阅读 &lt;a href=&#34;https://learnk8s.io/microservices-authentication-kubernetes&#34; target=&#34;_blank&#34;&gt;Authentication between microservices using Kubernetes identities&lt;/a&gt; 来里了解相关内容。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：Inspektor Gadget</title>
      <link>/post/intro-k8s-gadget/</link>
      <pubDate>Sun, 26 Jun 2022 14:51:18 +0800</pubDate>
      <guid>/post/intro-k8s-gadget/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;各位好，今天 6 月 26 号，吃了么您呐。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;例行查看 krew index 的时候，发现有个新插件 &lt;code&gt;gadgit&lt;/code&gt;，翻翻来历，居然是 &lt;a href=&#34;https://kinvolk.io/&#34; target=&#34;_blank&#34;&gt;Kinvolk&lt;/a&gt; 的作品，公司不太出名，印象里最早做服务网格 Benchmark 的就是他。插件功能介绍很简单：&lt;code&gt;Collection of gadgets for Kubernetes developers&lt;/code&gt;，但是用法就很了不得了，非常有字数越小事越大的感觉：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;Available Commands:
  advise      Recommend system configurations based on collected information
  audit       Audit a subsystem
  completion  generate the autocompletion script for the specified shell
  deploy      Deploy Inspektor Gadget on the cluster
  help        Help about any command
  profile     Profile different subsystems
  snapshot    Take a snapshot of a subsystem and print it
  top         Gather, sort and periodically report events according to a given criteria
  trace       Trace and print system events
  traceloop   Get strace-like logs of a pod from the past
  undeploy    Undeploy Inspektor Gadget from cluster
  version     Show version
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;过滤掉辅助功能，可以看到几个主要内容：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;advise：根据搜集信息，推荐系统配置内容&lt;/li&gt;
&lt;li&gt;audit：对子系统进行审计&lt;/li&gt;
&lt;li&gt;profile：对不同子系统进行侧写&lt;/li&gt;
&lt;li&gt;snapshot：给子系统进行快照并打印&lt;/li&gt;
&lt;li&gt;top：根据制定规则，搜集、排序和周期性地报告事件&lt;/li&gt;
&lt;li&gt;trace：跟踪和打印系统事件&lt;/li&gt;
&lt;li&gt;traceloop：获取类似 strace 格式的历史日志&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其实说了跟没说一样是不是？不如一条条看过去了。&lt;/p&gt;

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

&lt;p&gt;首先使用 krew 安装这个插件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl krew install gadget
Updated the local copy of plugin index.
Installing plugin: gadget
...
 | Use this plugin:
 |      kubectl gadget
...
 |  | $ kubectl gadget deploy | kubectl apply -f -
...
WARNING: You installed plugin &amp;quot;gadget&amp;quot; from the krew-index plugin repository.
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上文可以看到，使用插件之前要安装到及群里 &lt;code&gt;kubectl gadget deploy | kubectl apply -f -&lt;/code&gt;，可以看到，除了 RBAC 内容之外，还有 Daemonset 和 CRD 这两个东西。为了跟踪 Pod 行为，Inspektor Gadget 把 BPF 程序附加到内核函数上，当函数被执行时，内核也会运行这些被注入的程序。因此，BPF 程序需要检测触发该函数的系统调用，是否来自 Inspektor Gadget 的追踪目标。为了做到这一点，程序在包含要追踪的 Pod 列表的 BPF Map 中查找当前的 cgroup id，如果没有找到，程序会提前退出。最后，BPF程序收集要追踪的信息，例如，系统调用参数，并将它们 Ring Buffer 或 BPF Map。Inspektor Gadget 的用户空间工具在 Ring Buffer 或 BPF 地图上监听或读取，并获取新的事件。追踪结束后，BPF 程序将会被删除。&lt;/p&gt;

&lt;h2 id=&#34;network-policy-advise&#34;&gt;Network Policy Advise&lt;/h2&gt;

&lt;p&gt;这个功能由 Monitor 和 Report 两个部分构成，分别是启动特定命名空间内工作负载的网络监听，生成跟踪记录；以及根据跟踪记录生成网络策略两部分，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget advise network-policy monitor  --output /tmp/result.txt
Node &amp;quot;gke-gcp-vlab-k8s-default-pool-d3fe3442-pw6v&amp;quot; ready.
Node &amp;quot;gke-gcp-vlab-k8s-default-pool-d3fe3442-9hsc&amp;quot; ready.
Node &amp;quot;gke-gcp-vlab-k8s-default-pool-d3fe3442-nj0k&amp;quot; ready.
^C
Stopping...

$ more /tmp/result.txt
{&amp;quot;type&amp;quot;:&amp;quot;ready&amp;quot;}
{&amp;quot;type&amp;quot;:&amp;quot;ready&amp;quot;}
{&amp;quot;type&amp;quot;:&amp;quot;ready&amp;quot;}
{&amp;quot;type&amp;quot;:&amp;quot;connect&amp;quot;,&amp;quot;remote_kind&amp;quot;:&amp;quot;pod&amp;quot;,&amp;quot;port&amp;quot;:2021,&amp;quot;local_pod_namespace&amp;quot;:&amp;quot;gadget&amp;quot;,&amp;quot;local_pod_name&amp;quot;:&amp;quot;gadget-dzb7g&amp;quot;,&amp;quot;local_pod_labels&amp;quot;:{&amp;quot;controller-revision-hash&amp;quot;:&amp;quot;8f55cc94f&amp;quot;,&amp;quot;k8s-app&amp;quot;:&amp;quot;gadget&amp;quot;,&amp;quot;pod-template-generation&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;remote_pod_namespace&amp;quot;:&amp;quot;kube-system&amp;quot;,&amp;quot;remote_pod_name&amp;quot;:&amp;quot;pdcsi-node-lpqln&amp;quot;,&amp;quot;remote_pod_labels&amp;quot;:{&amp;quot;controller-revision-hash&amp;quot;:&amp;quot;69cdc7c487&amp;quot;,&amp;quot;k8s-app&amp;quot;:&amp;quot;gcp-compute-persistent-disk-csi-driver&amp;quot;,&amp;quot;pod-template-generation&amp;quot;:&amp;quot;1&amp;quot;},&amp;quot;debug&amp;quot;:&amp;quot;4649087588182 cpu#1 connect 3293 otelsvc 10.138.15.229:33032 10.138.15.229:2021 4026531992\n&amp;quot;}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;执行一段时间后使用 Ctrl+C 终止命令，可以看到指定的输出文件中包含了一堆类似 JSON 的记录内容，可以用这个文件生成网络策略：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget advise network-policy report --input=/tmp/result.txt
...
      podSelector:
        matchLabels:
          k8s-app: konnectivity-agent
    ports:
    - port: 10250
      protocol: TCP
  podSelector:
    matchLabels:
      k8s-app: gadget
  policyTypes:
  - Ingress
  - Egress
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，网络策略已经生成。&lt;/p&gt;

&lt;h2 id=&#34;seccomp-profile-advise&#34;&gt;Seccomp Profile Advise&lt;/h2&gt;

&lt;p&gt;这一功能是用 &lt;code&gt;advise seccomp-profile&lt;/code&gt; 模块完成的，这个模块有三个子命令，分别是 &lt;code&gt;start&lt;/code&gt;、&lt;code&gt;list&lt;/code&gt; 和 &lt;code&gt;stop&lt;/code&gt;，例如要跟踪一个 Calico Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget advise seccomp-profile start --podname=calico-node-t6hwg
HAmaTrPcxTLDNfSo
$ kubectl gadget advise seccomp-profile list
NAMESPACE      NODE(S)                                                                                                                         POD                  CONTAINER    TRACEID
kube-system    gke-gcp-vlab-k8s-default-pool-d3fe3442-9hsc,gke-gcp-vlab-k8s-default-pool-d3fe3442-nj0k,gke-gcp-vlab-k8s-default-pool-d3fe3442-pw6v    calico-node-t6hwg                 HAmaTrPcxTLDNfSo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面 &lt;code&gt;start&lt;/code&gt; 命令执行后出现的 &lt;code&gt;HAmaTrPcxTLDNfSo&lt;/code&gt; 就是跟踪 ID，开始一段时间之后，可以调用 &lt;code&gt;stop&lt;/code&gt; 命令结束跟踪，跟踪结束后会显示这个 Pod 的 Seccomp：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;kubectl gadget advise seccomp-profile stop HAmaTrPcxTLDNfSo
{
  &amp;quot;defaultAction&amp;quot;: &amp;quot;SCMP_ACT_ERRNO&amp;quot;,
  &amp;quot;architectures&amp;quot;: [
    &amp;quot;SCMP_ARCH_X86_64&amp;quot;,
    &amp;quot;SCMP_ARCH_X86&amp;quot;,
    &amp;quot;SCMP_ARCH_X32&amp;quot;
  ],
  &amp;quot;syscalls&amp;quot;: [
    {
      &amp;quot;names&amp;quot;: [
        &amp;quot;accept4&amp;quot;,
        &amp;quot;access&amp;quot;,
        &amp;quot;arch_prctl&amp;quot;,
...
 ],
      &amp;quot;action&amp;quot;: &amp;quot;SCMP_ACT_ALLOW&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;profile&#34;&gt;Profile&lt;/h2&gt;

&lt;p&gt;这个模块包括 &lt;code&gt;block-io&lt;/code&gt; 和 &lt;code&gt;cpu&lt;/code&gt; 两个指令，例如监控某个节点的 &lt;code&gt;block-io&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt; kubectl gadget profile block-io --node=gke-gcp-vlab-k8s-default-pool-d3fe3442-9hsc
Tracing block device I/O... Hit Ctrl-C to end.^C

     usecs               : count     distribution
         0 -&amp;gt; 1          : 0        |                                        |
         2 -&amp;gt; 3          : 0        |                                        |
         4 -&amp;gt; 7          : 0        |                                        |
         8 -&amp;gt; 15         : 0        |                                        |
        16 -&amp;gt; 31         : 0        |                                        |
        32 -&amp;gt; 63         : 0        |                                        |
        64 -&amp;gt; 127        : 1        |                                        |
       128 -&amp;gt; 255        : 1        |                                        |
       256 -&amp;gt; 511        : 0        |                                        |
       512 -&amp;gt; 1023       : 2        |                                        |
      1024 -&amp;gt; 2047       : 54       |****************                        |
      2048 -&amp;gt; 4095       : 44       |*************                           |
      4096 -&amp;gt; 8191       : 49       |***************                         |
      8192 -&amp;gt; 16383      : 128      |****************************************|
     16384 -&amp;gt; 32767      : 118      |************************************    |
     32768 -&amp;gt; 65535      : 11       |***                                     |
     65536 -&amp;gt; 131071     : 5        |*                                       |
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到以微秒为单位的统计记录和分布情况。&lt;code&gt;cpu&lt;/code&gt; 子命令的用法如下，其中 &lt;code&gt;-K&lt;/code&gt; 开关意思是只关注内核空间的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;kubectl gadget profile cpu -p calico-node-t6hwg -K
Capturing stack traces... Hit Ctrl-C to end.^C

calico-node;entry_SYSCALL_64_after_hwframe;do_syscall_64;ksys_write;vfs_write;pipe_write;__wake_up_sync_key;_raw_spin_unlock_irqrestore;_raw_spin_unlock_irqrestore 1
calico-node;entry_SYSCALL_64_after_hwframe;do_syscall_64;ksys_read;vfs_read;pipe_read;anon_pipe_buf_release;anon_pipe_buf_release 1
ip 1
calico-node;entry_SYSCALL_64_after_hwframe;do_syscall_64;__se_sys_nanosleep;get_timespec64;_copy_from_user;copy_user_generic_unrolled;copy_user_generic_unrolled 1
calico-node 9
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;snapshot&#34;&gt;Snapshot&lt;/h2&gt;

&lt;p&gt;Snapshot 模块分为 &lt;code&gt;process&lt;/code&gt; 和 &lt;code&gt;socket&lt;/code&gt; 两个子命令，分别用于记录进程和网络。（&lt;code&gt;v0.5.1&lt;/code&gt; 版本的 &lt;code&gt;process&lt;/code&gt; 子命令好像无法工作）。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget snapshot socket \  
    --node=gke-gcp-vlab-k8s-default-pool-d3fe3442-pw6v \
    -o custom-columns=namespace,pod,protocol,status
NAMESPACE       POD             PROTOCOL STATUS
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
kube-system     calico-node-zjpl5 TCP      ESTABLISHED
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;top&#34;&gt;Top&lt;/h2&gt;

&lt;p&gt;这个模块有三个子命令，&lt;code&gt;block-io&lt;/code&gt;、&lt;code&gt;tcp&lt;/code&gt; 和 &lt;code&gt;file&lt;/code&gt;，跟 Linux 系统的 &lt;code&gt;top&lt;/code&gt; 命令类似，例如下面的命令列出的 top file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget top file \
    -o custom-columns=container,pid,comm,reads
CONTAINER        PID     COMM             READS
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     2
gke-metrics-agent 56606   otelsvc          2
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     2
gke-metrics-agent 56606   otelsvc          2
fluentbit        3737    flb-pipeline     1
fluentbit        3737    flb-pipeline     2
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;trace&#34;&gt;Trace&lt;/h2&gt;

&lt;p&gt;这个模块针对系统事件进行跟踪，目前支持包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bind：Scoket 绑定&lt;/li&gt;
&lt;li&gt;capabilities：Capability 检查&lt;/li&gt;
&lt;li&gt;dns：DNS 请求&lt;/li&gt;
&lt;li&gt;exec：新进程&lt;/li&gt;
&lt;li&gt;fsslower：&lt;code&gt;open&lt;/code&gt;、&lt;code&gt;read&lt;/code&gt;、&lt;code&gt;write&lt;/code&gt; 和 &lt;code&gt;fsync&lt;/code&gt; 操作时长超过阈值&lt;/li&gt;
&lt;li&gt;mount：&lt;code&gt;mount&lt;/code&gt; 和 &lt;code&gt;umount&lt;/code&gt; 操作&lt;/li&gt;
&lt;li&gt;oomkill：OOM Killer 被触发&lt;/li&gt;
&lt;li&gt;open：&lt;code&gt;open&lt;/code&gt; 系统调用&lt;/li&gt;
&lt;li&gt;signal：跟踪进程收到的信号&lt;/li&gt;
&lt;li&gt;sni: TLS 请求中的 SNI&lt;/li&gt;
&lt;li&gt;tcp：TCP 的 &lt;code&gt;connect&lt;/code&gt;、&lt;code&gt;accept&lt;/code&gt; 和 &lt;code&gt;close&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;tcpconnect：&lt;code&gt;connect&lt;/code&gt; 调用&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如对 &lt;code&gt;open&lt;/code&gt; 的跟踪：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl gadget trace open -o custom-columns=container,path

CONTAINER        PATH
fluentbit        /var/log/containers
fluentbit        /var/log/pods
fluentbit        /var/log/containers
fluentbit        /var/log/pods
fluentbit        /var/run/google-fluentbit/pos-files
...
csi-driver-registrar /usr/bin/runc
csi-driver-registrar /sys/kernel/mm/hugepages
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;然后&#34;&gt;然后&lt;/h2&gt;

&lt;p&gt;以后没 eBPF 支持连 Ops 都不好做了？&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：Security Profiles Operator</title>
      <link>/post/intro-to-spo/</link>
      <pubDate>Sun, 20 Mar 2022 20:20:50 +0800</pubDate>
      <guid>/post/intro-to-spo/</guid>
      <description>

&lt;p&gt;在云原生安全方面，Kubernetes 在不同维度提供了很多的不同内容，例如 RBAC、Networkpolicy、SecurityContext 等等，种种措施中，像我这样基础不牢的 YAML 工程师最头大的可能就要数 SecurityContext 里面的 SELinux、Seccomp 和 AppArmor 三大块了。&lt;a href=&#34;https://github.com/kubernetes-sigs/security-profiles-operator&#34; target=&#34;_blank&#34;&gt;Security Profiles Operator&lt;/a&gt; 项目为此而来，希望能够降低在 Kubernetes 集群中使用这些安全技术的难度。在项目网页上转了转，发现他所说的简化，除了定义几个 CRD 封装这样的 Operator 传统技能之外；还有一个使用 CRD 在节点间传输 Security Profile 的能力；最后也是最重要的，提供了很方便的录制功能，这倒是真的戳中了痛点——手写 Profile 固然酷炫，录制生成才是生产力啊。目前支持的功能矩阵如下：&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;th&gt;Seccomp&lt;/th&gt;
&lt;th&gt;SELinux&lt;/th&gt;
&lt;th&gt;AppArmor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Profile CRD&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;ProfileBinding&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Deploy profiles into nodes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;WIP&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Remove profiles no longer in use&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;WIP&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Profile Auto-generation (logs)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;WIP&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Profile Auto-generation (ebpf)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Audit log enrichment&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;WIP&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

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

&lt;p&gt;如果目标环境不是 Openshift，首先需要安装 &lt;code&gt;Cert Manager&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.6.1/cert-manager.yaml

customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来安装 SPO：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/security-profiles-operator/master/deploy/operator.yaml
customresourcedefinition.apiextensions.k8s.io/profilebindings.security-profiles-operator.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/profilerecordings.security-profiles-operator.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/seccompprofiles.security-profiles-operator.x-k8s.io created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看生成的 CRD，大致功能如下&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;th&gt;功能&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AppArmorProfile&lt;/td&gt;
&lt;td&gt;aa&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;用于保存 AppArmor Profile&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;ProfileBinding&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;把 Profile 绑定到 Pod 上&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;ProfileRecording&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;用录制的方式生成 Profile&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;RawSelinuxProfile&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;SeccompProfile&lt;/td&gt;
&lt;td&gt;sp&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;用于保存 Seccomp Profile&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;SecurityProfileNodeStatus&lt;/td&gt;
&lt;td&gt;spns&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;SecurityProfilesOperatorDaemon&lt;/td&gt;
&lt;td&gt;spod&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;SelinuxProfile&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;用于保存 Selinux Profile&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;最后创建一个命名空间 &lt;code&gt;spo&lt;/code&gt;，并以此作为缺省命名空间，进行后续的试用过程。&lt;/p&gt;

&lt;h2 id=&#34;借助-spo-传递-seccomp&#34;&gt;借助 SPO 传递 Seccomp&lt;/h2&gt;

&lt;p&gt;创建一个 Seccomp Profile，其中加入了对系统调用的审计日志：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: security-profiles-operator.x-k8s.io/v1beta1
kind: SeccompProfile
metadata:
  namespace: spo
  name: seccomp-profile-sample
spec:
  defaultAction: SCMP_ACT_LOG
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Apply 之后 &lt;code&gt;kubectl get sp seccomp-profile-sample -o yaml&lt;/code&gt; ，会发现状态如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;metadata:
...
  finalizers:
  - gke-gcp-vlab-k8s-default-pool-7c61250b-x3h1-delete
  - gke-gcp-vlab-k8s-default-pool-7c61250b-n9l3-delete
  - gke-gcp-vlab-k8s-default-pool-7c61250b-86wz-delete
...
status:
...
  localhostProfile: operator/spo/seccomp-profile-sample.json
  status: Installed
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;表明 Profile 已经部署到三个节点，状态为 &lt;code&gt;Installed&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这样一来就可以创建一个 Pod，引用这个 Profile：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: sleep-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: operator/spo/seccomp-profile-sample.json
  containers:
    - name: sleep
      image: dustise/sleep:v0.9.6
      volumeMounts:
      - name: kubelet
        mountPath: /data/kubelet
  volumes:
  - name: kubelet
    hostPath:
        path: /var/lib/kubelet/seccomp/operator/spo
        type: Directory
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 创建之后，进入 Pod Shell，可以看到我们生成的 Profile：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl exec -it sleep-pod -- bash
bash-5.0# cd /data/kubelet
bash-5.0# ls
seccomp-profile-sample.json
bash-5.0# cat seccomp-profile-sample.json
{&amp;quot;defaultAction&amp;quot;:&amp;quot;SCMP_ACT_LOG&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来执行一下 CURL 点什么，登录节点看看日志&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ sudo journalctl -xe  | grep -i seccomp | grep curl
Mar 21 10:12:44 gke-gcp-vlab-k8s-default-pool-d97cb436-mdgb audit[180209]: SECCOMP auid=4294967295 uid=0 gid=0 ses=4294967295 subj==docker-default (enforce) pi
d=180209 comm=&amp;quot;curl&amp;quot; exe=&amp;quot;/usr/bin/curl&amp;quot; sig=0 arch=c000003e syscall=231 compat=0 ip=0x7f6d3b8d76f9 code=0x7ffc0000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;删掉 Pod。便于进行后续步骤&lt;/p&gt;

&lt;p&gt;如果我不想修改 Pod，可以用 ProfileBinding 把 Seccomp Profile 和镜像绑定到一起，如下配置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
kind: ProfileBinding
metadata:
  name: sample-binding
spec:
  profileRef:
    kind: SeccompProfile
    name: seccomp-profile-sample
  image: dustise/sleep:v0.9.6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看看重建一个没有 &lt;code&gt;securityPod&lt;/code&gt; 的 Pod 会有什么变化：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: sleep-pod
spec:
  containers:
    - name: sleep
      image: dustise/sleep:v0.9.6
      volumeMounts:
      - name: kubelet
        mountPath: /data/kubelet
  volumes:
  - name: kubelet
    hostPath:
        path: /var/lib/kubelet/seccomp/operator/spo
        type: Directory
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建之后，我们看看线上的 YAML：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get po sleep-pod -o yaml
apiVersion: v1
kind: Pod
...
    securityContext:
      seccompProfile:
        localhostProfile: operator/spo/seccomp-profile-sample.json
        type: Localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;果然这里被修改了。有兴趣还可以查查，这里用到的 Mutating Webhook。&lt;/p&gt;

&lt;h2 id=&#34;录制-profile&#34;&gt;录制 Profile&lt;/h2&gt;

&lt;p&gt;SPO 支持三种录制模式，分别是 &lt;code&gt;hook&lt;/code&gt;、&lt;code&gt;log&lt;/code&gt; 或 &lt;code&gt;eBPF&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hook&lt;/code&gt; 指的是 &lt;a href=&#34;https://github.com/opencontainers/runtime-spec/blob/fd895fb/config.md#posix-platform-hooks&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;OCI hooks&lt;/code&gt;&lt;/a&gt;，但是目前 &lt;a href=&#34;https://github.com/containerd/containerd/issues/6645&#34; target=&#34;_blank&#34;&gt;containerd 还不支持&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;log&lt;/code&gt; 则是使用基于日志的录制方式。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eBPF&lt;/code&gt; 自然就是最新的基于 eBPF 的录制了。&lt;/p&gt;

&lt;p&gt;缺省情况下 &lt;code&gt;spod&lt;/code&gt; 没有开启 eBPF 录制功能，需要把开关 &lt;code&gt;spec.enableBpfRecorder&lt;/code&gt; 设置为 &lt;code&gt;true&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl patch spod spod -n security-profiles-operator  --type=merge -p &#39;{&amp;quot;spec&amp;quot;:{&amp;quot;enableBpfRecorder&amp;quot;:true}}&#39;
securityprofilesoperatordaemon.security-profiles-operator.x-k8s.io/spod patched
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来创建一个录制对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
kind: ProfileRecording
metadata:
  name: sample-recording
spec:
  kind: SeccompProfile
  recorder: bpf
  podSelector:
    matchLabels:
      app: sleep
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交之后，我们给上一个 Pod 加上标签 &lt;code&gt;app=sleep&lt;/code&gt;，创建出来，然后随便执行点什么：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f recording.yaml
profilerecording.security-profiles-operator.x-k8s.io/sample-recording created
$ kubectl apply -f plain-pod.yaml
pod/sleep-pod created
$ kubectl exec -it sleep-pod -- curl http://jd.com
...
$ kubectl exec -it sleep-pod -- mkdir 123
...
$ kubectl exec -it sleep-pod -- ping baidu.com
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;删除 Pod，查看 SeccompProfile：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl delete -f plain-pod.yaml
pod &amp;quot;sleep-pod&amp;quot; deleted
$ kubectl get sp
NAME                     STATUS      AGE
sample-recording-sleep   Installed   9s

$ kubectl get sp sample-recording-sleep -o yaml

kind: SeccompProfile
metadata:
  creationTimestamp: &amp;quot;2022-03-21T14:07:23Z&amp;quot;
  finalizers:
  - gke-gcp-vlab-k8s-default-pool-d97cb436-5tct-delete
  - gke-gcp-vlab-k8s-default-pool-d97cb436-mdgb-delete
  - gke-gcp-vlab-k8s-default-pool-d97cb436-d8d1-delete
  generation: 1
  labels:
    spo.x-k8s.io/profile-id: SeccompProfile-sample-recording-sleep
...
spec:
  architectures:
  - SCMP_ARCH_X86_64
  defaultAction: SCMP_ACT_ERRNO
  syscalls:
  - action: SCMP_ACT_ALLOW
    names:
    - arch_prctl
    - bind
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里看到，删除 Pod 之后，录制过程自动生成了新的 SeccompProfile，其中包含了 Pod 工作过程中使用的配置，并且已经被安装到了各个节点之上。&lt;/p&gt;

&lt;h2 id=&#34;牢骚&#34;&gt;牢骚&lt;/h2&gt;

&lt;p&gt;Kubernetes 普及之后，新方向层出不穷，正如杨蒙恩说的——“遍地是大王，短暂又辉煌”，不过安全可能是目前确定性最高的一块内容，决不短暂。所谓安全无小事，没有网格、没有 Serverless 甚至没有多集群、经济性、混布都可以，没有安全可能就全盘皆输了；也不要总想着新瓶装旧酒，挑挑节点、固定一下 IP 就完事了，BMW 装上马鞍之后，丢的不只是风阻和车顶，至少车友群是不太容易混了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>用 Karmada 和 Pipy 搭建野生多集群</title>
      <link>/post/wild-fleet-with-karmada-and-pp/</link>
      <pubDate>Sat, 30 Oct 2021 07:56:56 +0800</pubDate>
      <guid>/post/wild-fleet-with-karmada-and-pp/</guid>
      <description>

&lt;p&gt;最近加入 CNCF 成为沙箱项目的 &lt;a href=&#34;https://github.com/karmada-io/karmada&#34; target=&#34;_blank&#34;&gt;Karmada&lt;/a&gt; 项目是一个开放的多 Kubernetes 管理工具，从 Kubernetes Federation &lt;sup&gt;1&lt;/sup&gt;&amp;frasl;&lt;sub&gt;2&lt;/sub&gt; 继承的大量经验，让这个项目有成为多集群管理首选产品的潜力。Karmada 具有开箱即用的跨集群调度能力，简单的使用 &lt;code&gt;PropagationPolicy&lt;/code&gt; 就能将 &lt;code&gt;Deployment&lt;/code&gt;、&lt;code&gt;Service&lt;/code&gt; 这样的原生 Kubernetes 对象在多个集群之间进行灵活的调度。&lt;/p&gt;

&lt;p&gt;工作负载的分配结束之后，还有个重要的问题就是流量分配了——多集群管理加上流量分配，会有很多有意思的事情可以完成，例如蓝绿灰度金丝雀，集群维护升级等等。实际包括各种网格、API 网关方案在内的很多产品，都有提供跨集群流量管理方案可用。这个例子里我选择了 Pipy 来搭档完成任务，Flomesh 出品的这个东西可塑性很强，除了轻量、快速之外，更重要的是能够用高级语言对流量进行编程，最新版本还为插件模型加入了图形交互界面。&lt;/p&gt;

&lt;p&gt;整个原型大致架构如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/karmada-pp.png&#34; alt=&#34;arch&#34; /&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;创建 Karmada 管理集群，并部署 Karmada。&lt;/li&gt;
&lt;li&gt;纳管两个集群，分别命名为 &lt;code&gt;member1&lt;/code&gt; 和 &lt;code&gt;member2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;部署 Deployment，并分布到两个集群&lt;/li&gt;
&lt;li&gt;部署 Service，用 NodePort 方式开放服务，分发到集群&lt;/li&gt;
&lt;li&gt;将暴露的服务端口同步给 Pipy，完成流量分发&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;部署-karmada-集群&#34;&gt;部署 Karmada 集群&lt;/h2&gt;

&lt;p&gt;Karmada 的远程部署需要有一个运行的 Kubernetes 集群，然后克隆代码库，运行其中的 &lt;code&gt;hack/remote-up-karmada.sh&lt;/code&gt; 脚本即可，命令行 &lt;code&gt;./remote-up-karmada.sh ~/.kube/config admin@v19&lt;/code&gt; 表示在 &lt;code&gt;~/.kube/config&lt;/code&gt; 配置文件中 &lt;code&gt;admin@v19&lt;/code&gt; 上下文所指代的集群中部署 Karmada 控制平面。&lt;/p&gt;

&lt;p&gt;部署之前，有两个需要注意的点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;如果要在 &lt;code&gt;arm64&lt;/code&gt; 平台上运行，可以将脚本中的镜像改为 &lt;code&gt;dustise/****:v0.0009-29-gc2030ca6&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果像我一样偏偏使用 NodePort 方式暴露 Karmada API Server，在 &lt;code&gt;deploy-karmada.sh&lt;/code&gt; 的 203 行 &lt;code&gt;installCRDs&lt;/code&gt; 语句之前加入一个 &lt;code&gt;read -p &amp;quot;Review your kubeconfig, then press [Enter] key to continue..&amp;quot;&lt;/code&gt;，脚本运行至此会暂停工作，修改 kubeconfig 文件中新出现的 &lt;code&gt;karmada-apiserver&lt;/code&gt; 上下文中的服务器即可，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- cluster:
  insecure-skip-tls-verify: true
  server: https://10.211.55.58:32016
name: karmada-apiserver
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;安装器会在集群中生成 &lt;code&gt;kamada-system&lt;/code&gt; 命名空间，在其中运行一个自己的 API Server，用于提供集群管理服务，并拉取认证信息到指定 &lt;code&gt;Kubeconfig&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;纳管集群&#34;&gt;纳管集群&lt;/h2&gt;

&lt;p&gt;完成部署之后，可以在 Karmada 项目的 &lt;a href=&#34;https://github.com/karmada-io/karmada/releases&#34; target=&#34;_blank&#34;&gt;Release 页面&lt;/a&gt;上下载 &lt;code&gt;karmada&lt;/code&gt; 用客户端 &lt;code&gt;karmadactl&lt;/code&gt; 来加入集群了，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$  kubectl karmada join member2 \
--cluster-kubeconfig=./total.yaml \
--cluster-context=admin@karmada2
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;为了方便使用，上面的命令把 karmada 客户端设置成为了 &lt;code&gt;krew&lt;/code&gt; 插件，实际上直接解压使用二进制也是等效的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;和部署控制平面的命令类似，Karmada CLI 也是使用指定 &lt;code&gt;kubeconfig&lt;/code&gt; 和上下文的方式，获取集群操作权限，把集群加入 Karmada。&lt;/p&gt;

&lt;p&gt;Karmada 加入新集群之后，会在它的管理面 API Server 中注册一个 Cluster 对象，下面是加入了两个集群之后的样子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get clusters
NAME      VERSION    MODE   READY   AGE
member1   v1.19.15   Push   True    13h
member2   v1.19.15   Push   True    13h
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;操作成员集群&#34;&gt;操作成员集群&lt;/h2&gt;

&lt;p&gt;既然是对象，就可以看看他葫芦里卖的是什么 YAML 了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: cluster.karmada.io/v1alpha1
kind: Cluster
metadata:
  creationTimestamp: &amp;quot;2021-10-14T11:51:54Z&amp;quot;
  finalizers:
  - karmada.io/cluster-controller
  generation: 1
  name: member1
...
spec:
  apiEndpoint: https://10.211.55.61:6443
  secretRef:
    name: member1
    namespace: karmada-cluster
  syncMode: Push
status:
  apiEnablements:
  - groupVersion: v1
    resources:
    - kind: Binding
      name: bindings
    - kind: ComponentStatus
      name: componentstatuses
    - kind: ConfigMap
      name: configmaps
    - kind: Endpoints
      name: endpoints
    ...
  conditions:
  - lastTransitionTime: &amp;quot;2021-10-14T13:52:31Z&amp;quot;
    message: cluster is reachable and health endpoint responded with ok
    reason: ClusterReady
    status: &amp;quot;True&amp;quot;
    type: Ready
  kubernetesVersion: v1.19.15
  nodeSummary:
    readyNum: 1
    totalNum: 1
  resourceSummary:
    allocatable:
      cpu: &amp;quot;2&amp;quot;
      ephemeral-storage: &amp;quot;59200992363&amp;quot;
      hugepages-1Gi: &amp;quot;0&amp;quot;
      hugepages-2Mi: &amp;quot;0&amp;quot;
      hugepages-32Mi: &amp;quot;0&amp;quot;
      hugepages-64Ki: &amp;quot;0&amp;quot;
      memory: 1927288Ki
      pods: &amp;quot;110&amp;quot;
    allocated:
      cpu: &amp;quot;1&amp;quot;
      ephemeral-storage: &amp;quot;0&amp;quot;
      memory: 140Mi
      pods: &amp;quot;10&amp;quot;
    allocating:
      cpu: &amp;quot;0&amp;quot;
      ephemeral-storage: &amp;quot;0&amp;quot;
      memory: &amp;quot;0&amp;quot;
      pods: &amp;quot;0&amp;quot;

&lt;/code&gt;&lt;/pre&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;code&gt;spec&lt;/code&gt;字段中的内容则包含了集群的访问端点和一个对 Secret 对象的引用，查看一下其中的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$  kubectl view-secret -n karmada-cluster member1
Multiple sub keys found. Specify another argument, one of:
-&amp;gt; caBundle
-&amp;gt; token
$ kubectl view-secret -n karmada-cluster member1 caBundle
-----BEGIN CERTIFICATE-----
MIIC5zCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
...
-----END CERTIFICATE-----
$ kubectl view-secret -n karmada-cluster member1 token
eyJhbGciOiJSUzI1NiIsImtpZCI6ImRqZTY2OGVua0ltSHA2UGJ3LUZHQ0V
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看起来这是个访问 Kubernetes 使用的凭据，我们可以导出 caBundle 测试一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl --certificate-authority=member1/caBundle.pem \
  --server=https://10.211.55.61:6443 --token=eyJhb...\
  get nodes
NAME       STATUS   ROLES    AGE   VERSION
karmada1   Ready    master   37h   v1.19.15
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;果然返回了集群信息。&lt;/p&gt;

&lt;p&gt;Karmada v0.9.0 中的 Cluster 对象属于 &lt;code&gt;cluster.karmada.io/v1alpha1&lt;/code&gt;，因此可以用如下代码获取集群信息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;crd = client.CustomObjectsApi(karmada_config)
member_list = crd.list_cluster_custom_object(group=&amp;quot;cluster.karmada.io&amp;quot;,
                                             version=&amp;quot;v1alpha1&amp;quot;, plural=&amp;quot;clusters&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而根据前面的尝试，用这个结果连接集群也是很方便的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;secret_name = member_obj[&amp;quot;spec&amp;quot;][&amp;quot;secretRef&amp;quot;][&amp;quot;name&amp;quot;]
secret_ns = member_obj[&amp;quot;spec&amp;quot;][&amp;quot;secretRef&amp;quot;][&amp;quot;namespace&amp;quot;]
secret_client = client.CoreV1Api(karmada_config)
secret_obj = secret_client.read_namespaced_secret(secret_name, secret_ns)
server_token = secret_obj.data[&amp;quot;token&amp;quot;]
server = member_obj[&amp;quot;spec&amp;quot;][&amp;quot;apiEndpoint&amp;quot;]
cfg = config.kube_config.Configuration()
cfg.host = server
cfg.api_key = {&#39;authorization&#39;: &#39;Bearer &#39; + base64.b64decode(server_token.encode(&amp;quot;ascii&amp;quot;)).decode(&amp;quot;ascii&amp;quot;)}
cfg.verify_ssl = False
api_client = client.api_client.ApiClient(cfg)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;部署应用&#34;&gt;部署应用&lt;/h2&gt;

&lt;p&gt;创建一个 Deployment：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create deploy flask --image=dustise/flaskapp:v0.2.7
...
deployment.apps/flask scaled
$ kubectl get deploy
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
flask   0/4     0            0           13h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里我们创建一个 Deployment，并设置为四副本运行，在控制面看来，这个 Deployment 无法运行，也没有生成 Replicaset，接下来我们为它设置一个传播策略：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: flask
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
      name: flask
  placement:
    clusterAffinity:
      clusterNames:
        - member1
        - member2
    replicaScheduling:
      replicaDivisionPreference: Weighted
      replicaSchedulingType: Divided
      weightPreference:
        staticWeightList:
          - targetCluster:
              clusterNames:
                - member1
            weight: 1
          - targetCluster:
              clusterNames:
                - member2
            weight: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交之后，可以看到成员集群按照我们设置的权重来创建 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ k1 get po ; k2 get po
NAME                     READY   STATUS    RESTARTS   AGE
flask-6d75654674-54c7p   1/1     Running   0          34s
flask-6d75654674-qgbjg   1/1     Running   0          34s
NAME                     READY   STATUS    RESTARTS   AGE
flask-6d75654674-7d5vl   1/1     Running   0          3s
flask-6d75654674-9ns6n   1/1     Running   0          112s
~~~
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;缩容到 2 实例：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;k1 get po ; k2 get po
NAME                     READY   STATUS        RESTARTS   AGE
flask-6d75654674-54c7p   0/1     Terminating   0          2m14s
flask-6d75654674-qgbjg   1/1     Running       0          2m14s
NAME                     READY   STATUS    RESTARTS   AGE
flask-6d75654674-9ns6n   1/1     Running   0          3m32s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 运行起来之后，我们来部署一个 Service：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Service
metadata:
  labels:
    app: flask
  name: flask
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: flask
  type: NodePort
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建之后，会发现 Karmada 为新服务分配了端口，Endpoint 是没有的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
flask        NodePort    10.110.144.229   &amp;lt;none&amp;gt;        80:31193/TCP   13h
kubernetes   ClusterIP   10.96.0.1        &amp;lt;none&amp;gt;        443/TCP        41h
$ kubectl get ep
NAME         ENDPOINTS           AGE
kubernetes   10.211.55.58:5443   41h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;同样创建一个 pp，把 Service 分配给集群。完成分发后就可以访问各个集群的服务了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;有时候有人跟你说 PP 其实不是耍流氓，是在讨论 Karmada。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;用-pipy-做负载均衡&#34;&gt;用 Pipy 做负载均衡&lt;/h2&gt;

&lt;p&gt;Pipy 的部署很方便，在 Release 页面下载二进制即可。源码中的 &lt;code&gt;tutorial/08-load-balancing-improved&lt;/code&gt; 就是一个负载均衡的例子，这里我们对其配置做个简化：&lt;/p&gt;

&lt;p&gt;这个例子中的 &lt;code&gt;router&lt;/code&gt; 对象和 Kong Gateway 的概念类似，我们修改一下 &lt;code&gt;config/router.json&lt;/code&gt;，仅包含一个对 &lt;code&gt;/env/*&lt;/code&gt; 路径的转发：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;routes&amp;quot;: {
    &amp;quot;/env/*&amp;quot;: &amp;quot;flask&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而 &lt;code&gt;config/balancer.js&lt;/code&gt; 中则包含了对负载均衡池的定义，原文内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;services&amp;quot;: {
    &amp;quot;service-hi&amp;quot;      : [&amp;quot;127.0.0.1:8080&amp;quot;, &amp;quot;127.0.0.1:8082&amp;quot;],
    &amp;quot;service-echo&amp;quot;    : [&amp;quot;127.0.0.1:8081&amp;quot;],
    &amp;quot;service-tell-ip&amp;quot; : [&amp;quot;127.0.0.1:8082&amp;quot;]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;清理一下，只留下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;services&amp;quot;: {
    &amp;quot;flask&amp;quot;      : [&amp;lt;每个集群每个节点的 Flask 服务 NodePort&amp;gt;] 
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;启动 Pipy：&lt;code&gt;pipy --admin-port=8889 proxy.js&lt;/code&gt;，在 8889 启用了控制台端口，浏览器打开会看到类似界面：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/pipy-console.png&#34; alt=&#34;console&#34; /&gt;&lt;/p&gt;

&lt;p&gt;现在就可以在 &lt;code&gt;config/proxy.json&lt;/code&gt; 中定义的代理端口中上访问我们部署在 Kubernetes 中的服务了。整个流程都非常清楚，这个服务发生任何变化，把新的开放端口写入配置，刷新 Pipy 即可，大致代码如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# 列出 Karmada 集群
for cluster in members[&amp;quot;items&amp;quot;]:
    member_name = cluster[&amp;quot;metadata&amp;quot;][&amp;quot;name&amp;quot;]
    logging.info(&amp;quot;Services in cluster {}&amp;quot;.format(member_name))
    cluster_client = karmada.get_member_client(cluster)
    # 查找服务
    services = karmada.list_service(cluster_client, &amp;quot;default&amp;quot;, {&amp;quot;app&amp;quot;: &amp;quot;flask&amp;quot;})
    # 每个集群的节点
    nodes = karmada.list_nodes(cluster_client)
    address_list = []
    port_list = []
    for node in nodes.items:
        addresses = node.status.addresses
        for address in addresses:
            if address.type == &amp;quot;InternalIP&amp;quot;:
                logging.info(&amp;quot;Found a node in {} with IP {}&amp;quot;.format(member_name, address.address))
                address_list.append(address.address)
                break
    for service in services.items:
        port_list.append(service.spec.ports[0].node_port)
        logging.info(&amp;quot;Found service named {} in cluster {}&amp;quot;.format(service.metadata.name, member_name))
    # 组装 URL
    for address in address_list:
        for port in port_list:
            lb_config[&amp;quot;services&amp;quot;][&amp;quot;flask&amp;quot;].append(&amp;quot;{}:{}&amp;quot;.format(address, port))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最终结果写入 Pipy 配置，管理页面重载配置就完成了刷新。此时访问 Pipy 在 pr​oxy.js 中定义的端口，就能看到负载均衡的效果了。&lt;/p&gt;

&lt;h2 id=&#34;其实没这么简单&#34;&gt;其实没这么简单&lt;/h2&gt;

&lt;p&gt;首先，我知道该用 watch ：）；&lt;/p&gt;

&lt;p&gt;其次，在管理界面刷新配置是个挺傻的事情，Pipy 提供了 &lt;a href=&#34;https://mp.weixin.qq.com/s/cDhPtNdng8_YZQpyFo5czw&#34; target=&#34;_blank&#34;&gt;Repo&lt;/a&gt; 功能，可以进行热加载。这个组件也是 Pipy 高可用和控制平面、GitOps 的命门所在。&lt;/p&gt;

&lt;p&gt;另外，在节点比较多的集群中，往往不会把所有节点用于暴露 NodePort，这时可以考虑使用节点标签来限制负载均衡池的生成情况。&lt;/p&gt;

&lt;p&gt;最后直接生成 &lt;code&gt;balancer.js&lt;/code&gt; 是个非常粗糙的行为，这种做法里，Pipy 必须独占，否则一次刷新可能就覆盖了其它服务的定义，因此这里最好能够使用 Annotation 或者 CRD 等方式，给集群、服务、节点做出标识，从而精确完成刷新过程。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Karmada&lt;/strong&gt;：&lt;code&gt;https://github.com/karmada-io/karmada&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipy&lt;/strong&gt;：&lt;code&gt;https://github.com/flomesh-io/pipy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Pod 对象也能被淘汰么</title>
      <link>/post/could-pod-become-deprecated/</link>
      <pubDate>Sun, 24 Oct 2021 22:33:16 +0800</pubDate>
      <guid>/post/could-pod-become-deprecated/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://martinheinz.dev/blog/53&#34; target=&#34;_blank&#34;&gt;Could Kubernetes Pods Ever Become Deprecated?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://martinheinz.dev/&#34; target=&#34;_blank&#34;&gt;Martin Heinz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;随着时间的推移，所有的软件项目都会加入新的功能和 API，与此相对地，也会有功能和 API 被移除。Kubernetes 这样的大型项目也并无不同，但是核心 API 的废弃和删除，始终有些含混，Kubernetes 中的核心对象或者说是 API，例如 Pod、Deployment 和 Service，是不是可以删除呢？如果答案是肯定的，那么该如何进行呢？&lt;/p&gt;

&lt;h2 id=&#34;长话短说&#34;&gt;长话短说&lt;/h2&gt;

&lt;p&gt;GA 状态的核心 API，例如 &lt;code&gt;v1&lt;/code&gt; API 中的对象也是可能淘汰的。Kubernetes 中的的淘汰话题需要分为 API、CLI 以及 FeatureGate 这三个方面，每方面又会有自己的成熟阶段，例如 Alpha、Beta 或者 GA，这些成熟度的定义，就代表了在什么时间、什么条件下进行淘汰操作—— Pod 这样的东西也不能例外。因此本文尝试对这一问题进行进一步的探讨，看看过往的例子和一些未来的假设。&lt;/p&gt;

&lt;h2 id=&#34;分而治之&#34;&gt;分而治之&lt;/h2&gt;

&lt;p&gt;不同的对象或功能有不同的规则，所以在讨论淘汰规则之前，首先对这些淘汰目标进行分类：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST 对象&lt;/strong&gt;：这是绝大多数人最多打交道的东西，因此也是最引人关注的方向，这里包括了 Pod 或者 Deployment 这样的顶层对象，也包含了它们的成员字段，例如 &lt;code&gt;containers&lt;/code&gt;、&lt;code&gt;volumes&lt;/code&gt; 或者 &lt;code&gt;env&lt;/code&gt;；另外还有一些常量，例如 &lt;code&gt;imagePullPolicy&lt;/code&gt; 使用的 &lt;code&gt;Always&lt;/code&gt;、&lt;code&gt;IfNotPresent&lt;/code&gt; 等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLI 和命令行参数&lt;/strong&gt;：这一部分内容是针对客户端的。最容易想到的可能就是 &lt;code&gt;kubectl&lt;/code&gt;，其实还包含了 &lt;code&gt;kubelet&lt;/code&gt;、&lt;code&gt;kube-apiserver&lt;/code&gt; 或者 &lt;code&gt;kube-scheduler&lt;/code&gt; 及其子命令和选项等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能和行为&lt;/strong&gt;：各种不同成熟度的试验特性是无法用 API 或者 CLI 来表达的，但是它们也应该有自己的淘汰过程和节奏。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;指标&lt;/strong&gt;：最后 Kubernetes 的各个组件还在 &lt;code&gt;/metrics&lt;/code&gt; 端点中提供了大量指标。这些指标可能会在监控等系统中使用，因此也不能直接删除，而需要有一定的淘汰规则。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;rest-对象&#34;&gt;REST 对象&lt;/h2&gt;

&lt;p&gt;REST API 需要遵守一个普遍规则——官宣淘汰之时，API 版本至少要支持：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GA：12 个月或者 3 次发版（取最长时间）&lt;/li&gt;
&lt;li&gt;Beta：9 个月或者 3 次发版（取最长时间）&lt;/li&gt;
&lt;li&gt;Alpha：0 次发版&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;看起来好像非常清晰，其实里面包含了很多其它（可能很难理解）的规则，所以我们先进入示例环节来进行澄清。假设有一个叫做 &lt;code&gt;Task&lt;/code&gt; 的 API 对象（有趣的事实：这是 &lt;code&gt;Pod&lt;/code&gt; 的原名，请参见 &lt;a href=&#34;https://github.com/boddumanohar/kubernetes-first-commit/blob/2c4b3a562ce34cddc3f8218a2c4d11c7310e6d56/pkg/client/client.go#L19&#34; target=&#34;_blank&#34;&gt;First Commit of Kubernetes&lt;/a&gt;）。这个对象处于 GA 状态，其 API 版本为 &lt;code&gt;v1&lt;/code&gt;，淘汰需要经过什么过程呢？&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kubernetes 版本&lt;/th&gt;
&lt;th&gt;API 版本&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;X&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;此时 &lt;code&gt;Task&lt;/code&gt; 对象处于 GA 状态，并没有进入淘汰周期&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2alpha1&lt;/code&gt;, &lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;引入 &lt;code&gt;v2alpha1&lt;/code&gt;，宣布 &lt;code&gt;Task&lt;/code&gt; 开始淘汰，此时 &lt;code&gt;v2alpha1&lt;/code&gt; 中并不包含 &lt;code&gt;Task&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2alpha2&lt;/code&gt;，&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 &lt;code&gt;v2alpha2&lt;/code&gt; 替代 &lt;code&gt;v2alpha1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2beta1&lt;/code&gt;, &lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2alpha2&lt;/code&gt; 被 &lt;code&gt;v2beta1&lt;/code&gt; 替换&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2beta2&lt;/code&gt;、&lt;del&gt;&lt;code&gt;v2beta1&lt;/code&gt;&lt;/del&gt;、&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;引入 &lt;code&gt;v2beta2&lt;/code&gt;，&lt;code&gt;v2beta1&lt;/code&gt; 依旧存在，但是开始淘汰&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;、&lt;del&gt;&lt;code&gt;v2beta2&lt;/code&gt;&lt;/del&gt;、&lt;del&gt;&lt;code&gt;v2beta1&lt;/code&gt;&lt;/del&gt;、&lt;del&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;引入 &lt;code&gt;v2&lt;/code&gt;，包括首选使用的 &lt;code&gt;v1&lt;/code&gt; 在内的所有其他版本进入淘汰周期&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;、&lt;del&gt;&lt;code&gt;v2beta2&lt;/code&gt;&lt;/del&gt;、&lt;del&gt;&lt;code&gt;v2beta1&lt;/code&gt;&lt;/del&gt;、&lt;del&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;没有移除任何 API，但是 &lt;code&gt;v2&lt;/code&gt; 已经成为首选版本&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;、&lt;del&gt;&lt;code&gt;v2beta2&lt;/code&gt;&lt;/del&gt;、&lt;del&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;移除 &lt;code&gt;v2beta1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;、&lt;del&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;移除 &lt;code&gt;v2beta2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;、&lt;del&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;没有什么变化，按照规则，&lt;code&gt;v1&lt;/code&gt; 必须继续存活一个版本&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;X+10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;最终删除了 &lt;code&gt;v1&lt;/code&gt;，其中的 &lt;code&gt;Task&lt;/code&gt; 对象也宣告终结&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;从上表来看，如果在 &lt;code&gt;v2alpha1&lt;/code&gt; 开始淘汰 &lt;code&gt;Task&lt;/code&gt; 对象，就需要 9 个版本才能最终完成。读者需要注意的是，根据&lt;a href=&#34;https://github.com/kubernetes/enhancements/tree/master/keps/sig-release/2572-release-cadence&#34; target=&#34;_blank&#34;&gt;当下的发布节奏&lt;/a&gt;，每年发版三次，整个淘汰流程可能需要三年多。&lt;/p&gt;

&lt;p&gt;有些对象虽然没进入 GA，但是用户已经将其视为 GA 并进行使用。例如 &lt;a href=&#34;https://opensource.googleblog.com/2020/09/kubernetes-ingress-goes-ga.html&#34; target=&#34;_blank&#34;&gt;1.19 中才进入 GA 的 Ingress&lt;/a&gt;，或者 &lt;a href=&#34;https://kubernetes.io/blog/2021/04/09/kubernetes-release-1.21-cronjob-ga/&#34; target=&#34;_blank&#34;&gt;1.21 的 CronJob&lt;/a&gt;。这种 &lt;code&gt;beta&lt;/code&gt; 甚至是 &lt;code&gt;alpha&lt;/code&gt; 的版本，淘汰节奏就不会这么宽松了。要检查资源所属的分类，可以运行 &lt;code&gt;kubectl api-resources | grep beta&lt;/code&gt;，读取所有集群中的所有 &lt;code&gt;beta&lt;/code&gt; API 对象类型。&lt;/p&gt;

&lt;p&gt;REST 对象字段成员、常量以及对象结构，淘汰规则跟对象是一致的。也就是说，&lt;code&gt;imagePullPolicy&lt;/code&gt; 中使用的 &lt;code&gt;Always&lt;/code&gt;、&lt;code&gt;IfNotPresent&lt;/code&gt; 和 &lt;code&gt;Never&lt;/code&gt; 不会随机变化也不会从一节挪到另一节。&lt;/p&gt;

&lt;p&gt;例如 &lt;code&gt;PodSecurityPolicy&lt;/code&gt; 可能是近期的一个最大变化。这个 API 对象会从 &lt;code&gt;v1beta1&lt;/code&gt; 转向 EOL，在 &lt;code&gt;v1.21&lt;/code&gt; 中开始淘汰，在 &lt;code&gt;v1.25&lt;/code&gt; 中被移除。详情可参见 &lt;a href=&#34;https://github.com/kubernetes/enhancements/blob/master/keps/sig-auth/2579-psp-replacement/README.md/&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;KEP_2579&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;另一个进行中的重要淘汰就是 &lt;code&gt;selfLink&lt;/code&gt; 字段，这是 &lt;a href=&#34;https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/1164-remove-selflink&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;KEP-1164&lt;/code&gt;&lt;/a&gt; 中的一部分，这一变更的过程记录在 &lt;a href=&#34;https://github.com/kubernetes/enhancements/issues/1164&#34; target=&#34;_blank&#34;&gt;Github Issue&lt;/a&gt; 之中。&lt;/p&gt;

&lt;p&gt;如果你有兴趣了解其它的淘汰过程，希望了解其中的逻辑关系以及整个流程，可以在 &lt;a href=&#34;https://github.com/kubernetes/enhancements&#34; target=&#34;_blank&#34;&gt;kubernetes/enhancements repository&lt;/a&gt; 搜索包含 &lt;code&gt;deprecate&lt;/code&gt; 关键字的 KEP。&lt;/p&gt;

&lt;h2 id=&#34;客户端和参数&#34;&gt;客户端和参数&lt;/h2&gt;

&lt;p&gt;和 REST 对象类似，&lt;code&gt;kubectl&lt;/code&gt; 和 &lt;code&gt;kubelet&lt;/code&gt; 的子命令及其参数也是有自己的淘汰策略的。&lt;/p&gt;

&lt;p&gt;这部分的规则比前面的案例要简单，对于 &lt;code&gt;kubectl&lt;/code&gt; 这样的面对客户的组件来说：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GA：12 个月或者两次发版（取最长时间）&lt;/li&gt;
&lt;li&gt;Beta：3 个月或者 1 次发版（取最长时间）&lt;/li&gt;
&lt;li&gt;Alpha：0 次发版&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于 &lt;code&gt;kubelet&lt;/code&gt;、&lt;code&gt;kube-apiserver&lt;/code&gt; 或者 &lt;code&gt;kube-scheduler&lt;/code&gt; 这样的面向管理员的组件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GA：12 个月或者两次发版（取最长时间）&lt;/li&gt;
&lt;li&gt;Beta：3 个月或者 1 次发版（取最长时间）&lt;/li&gt;
&lt;li&gt;Alpha：0 次发版&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;近期这方面的最知名案例应该算是 &lt;code&gt;kubelet&lt;/code&gt; 中的 &lt;code&gt;dockershim&lt;/code&gt; 了。在 &lt;a href=&#34;https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2221-remove-dockershim&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;KEP-2221&lt;/code&gt;&lt;/a&gt; 中讲到，在 &lt;code&gt;v1.20&lt;/code&gt; 中设置淘汰，在 &lt;code&gt;v1.24&lt;/code&gt; 中进行删除。&lt;/p&gt;

&lt;p&gt;这方面的另一个显著目标就是 &lt;code&gt;seccomp&lt;/code&gt; Profile 即将 &lt;code&gt;GA&lt;/code&gt;，这一过程在 &lt;a href=&#34;https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/135-seccomp/README.md&#34; target=&#34;_blank&#34;&gt;KEP-135&lt;/a&gt; 中进行跟进。这个特性并不会真正地对参数和 CLI 产生影响，但是它的 &lt;code&gt;GA&lt;/code&gt; 过程会要求淘汰 &lt;code&gt;kubelet&lt;/code&gt; 的 &lt;code&gt;--seccomp-profile-root&lt;/code&gt;，详情请参见 &lt;a href=&#34;https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/135-seccomp/README.md#seccomp-root-path-configuration&#34; target=&#34;_blank&#34;&gt;SIG Node 文档&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;所以这一节的淘汰过程是比较较宽松的，但是如果你正在自动化过程中使用 &lt;code&gt;kubectl alpha&lt;/code&gt;，最好在升级集群和 CLI 之前检查一下它的淘汰情况。&lt;/p&gt;

&lt;h2 id=&#34;feature-gate&#34;&gt;Feature Gate&lt;/h2&gt;

&lt;p&gt;Kubernetes 每个版本中都会包含很多的实验性功能。这些功能被称为 &lt;code&gt;Feature Gate&lt;/code&gt;，它们使用 Key/Value 形式的开关进行控制。&lt;/p&gt;

&lt;p&gt;这些特性既然是用于试验的，其淘汰策略自然和其它的 Kubernetes 对象有所不同。随着特性的逐步成熟，它的 Feature Gate 也会发生变化。Alpha 阶段的功能，其 Feature Gate 会被缺省关闭；而 Beta 阶段的功能则会缺省打开；如果该功能进入 GA，对应的 Feature Gate 就不再需要了，会被淘汰，无法操作。&lt;/p&gt;

&lt;p&gt;Alpha 功能可能随时消失；Beta 功能可能会在 1 次发版以后删除；进入 GA 的功能则会在两次发版后删除。&lt;/p&gt;

&lt;p&gt;这方面的最好例子就是&lt;a href=&#34;https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/&#34; target=&#34;_blank&#34;&gt;官方的 Feature Gate 列表&lt;/a&gt;。以其中包含的 &lt;code&gt;AffinityInAnnotations&lt;/code&gt; 为例，它就是从 Alpha 淘汰的；而 &lt;code&gt;BlockVolume&lt;/code&gt;、&lt;code&gt;DryRun&lt;/code&gt; 或者 &lt;code&gt;EndpointSlice&lt;/code&gt; 则已经进入了 GA。我还没有看到过有从 Beta 被淘汰的 Feature Gate。&lt;/p&gt;

&lt;p&gt;如果打开了某些 Feature Gate，在集群升级之前一定要检查一下，防止一些特性因升级被删除。&lt;/p&gt;

&lt;h2 id=&#34;指标&#34;&gt;指标&lt;/h2&gt;

&lt;p&gt;最后一个需要关注的就是监控指标，可能会有监控工具对指标进行聚合和消费，因此其淘汰过程也是需要多加小心的。和前面章节中的内容不同，指标只分成两类——稳定和 Alpha，声明淘汰之后，稳定指标可以在 3 次发版之后移除，Alpha 可以随时移除。&lt;/p&gt;

&lt;p&gt;例如 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/83836&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;rest_client_request_latency_seconds&lt;/code&gt;&lt;/a&gt; 就是一个值得观察的指标淘汰案例，这个过程在 &lt;a href=&#34;https://v1-17.docs.kubernetes.io/docs/setup/release/notes/#deprecated-changed-metrics&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;v1.17&lt;/code&gt; 的版本说明&lt;/a&gt;里体现。&lt;/p&gt;

&lt;p&gt;如果想要了解更多监控指标生命周期的问题，可以查看一下&lt;a href=&#34;https://kubernetes.io/docs/concepts/cluster-administration/system-metrics/&#34; target=&#34;_blank&#34;&gt;系统指标的相关文档&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;那么这篇文章的要点在哪里：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;所有东西都可能淘汰？是的？&lt;/li&gt;
&lt;li&gt;需要担心吗？显然不用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;看看淘汰的时间线长度，就知道无需担心突然袭击了。但是为长远计，应该检查版本说明，注意其中是否有你正在使用的 Alpha 功能。还应该阅读 &lt;a href=&#34;https://kubernetes.io/docs/reference/using-api/deprecation-guide/&#34; target=&#34;_blank&#34;&gt;淘汰 API 指南&lt;/a&gt;，其中会列出所有未来将要移除的 API 对象。最后要说明的是，外部供应商的 CRD 的生命周期是自行负责的，可能和官方策略并不一致。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes Semaphore：模块化、无侵入的跨集群通信框架</title>
      <link>/post/k8s-semaphore-intro/</link>
      <pubDate>Thu, 07 Oct 2021 16:58:34 +0800</pubDate>
      <guid>/post/k8s-semaphore-intro/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://uw-labs.github.io/blog/kubernetes,/multicluster/2021/07/21/kube-semaphore-intro.html&#34; target=&#34;_blank&#34;&gt;Kubernetes Semaphore: A modular and nonintrusive framework for cross cluster communication&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://uw-labs.github.io/blog/&#34; target=&#34;_blank&#34;&gt;UW Labs&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;问题&#34;&gt;问题&lt;/h2&gt;

&lt;p&gt;我们有一个环境，其中包含分属三个不同供应商（AWS、GCP 和私有云）的三个集群，我们希望不同集群中运行的应用能够互相通信，以及：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;跨集群的 Pod 网络和加密能力；&lt;/li&gt;
&lt;li&gt;访问远端 Kubernetes Service 的能力；&lt;/li&gt;
&lt;li&gt;用策略来放行远端集群特定应用，使之能够访问本地端点。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们有一个跨集群的三层网络，这样三个集群的节点就能互相访问了。每个集群都是在各个供应商子网中申请的节点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS：10.66.21.0/24&lt;/li&gt;
&lt;li&gt;GCP：10.22.20.0/24&lt;/li&gt;
&lt;li&gt;私有云：10.88.0.0/24&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;三个集群的 Pod 网络分配如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS：10.2.0.0/16&lt;/li&gt;
&lt;li&gt;GCP：10.4.0.0/16&lt;/li&gt;
&lt;li&gt;私有云：10.6.0.0/16&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;依赖项&#34;&gt;依赖项&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Calico CNI：在所有集群中使用 Calico CNI，方案中对 Calico 具有一定依赖；&lt;/li&gt;
&lt;li&gt;CoreDNS：Semaphore-Service-Mirror 对此有依赖；&lt;/li&gt;
&lt;li&gt;跨集群的三层网络。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;现有方案&#34;&gt;现有方案&lt;/h2&gt;

&lt;p&gt;我们对 Istio、Linkerd、Consul 进行了评估，还直接编写了自己的 Envoy Proxy 配置工具。这些方案都能提供上述大部分甚至全部的功能。但是我们认为他们的性价比并不完全适合我们的环境。不同集群之中运行的不同应用构成的网格结构并不总是让人有兴趣的，其中提供的大量功能也无法让所有人受益。我们希望避免使用 Sidecar Proxy，也回避随之而来的额外开销，并确保我们的程序保持尽可能的独立，不受跨集群通信解决方案的影响。&lt;/p&gt;

&lt;h2 id=&#34;设计&#34;&gt;设计&lt;/h2&gt;

&lt;p&gt;上面既然提到要避开 Sidecar 代理，我们希望用一种对运维人员和用户都较为简单的方式来解决问题。&lt;/p&gt;

&lt;p&gt;理想情况下，每个目标都应该以相互隔离的方法来达成——假设用户只是需要对 Pod 通信进行加密，那么只要进行单独的部署即可。对于新用户来说，只需要少量的配置就可以对方案进行试用，并且可以轻松回退。&lt;/p&gt;

&lt;h2 id=&#34;方案&#34;&gt;方案&lt;/h2&gt;

&lt;p&gt;Kube-Semaphore 是一个轻量级框架，为不同 Kubernetes 集群之间的应用，提供了简单安全的通信能力，并且无需对应用和清单进行修改。&lt;/p&gt;

&lt;p&gt;这不是一个服务网格方案，而是要为远端集群提供服务端点和防火墙规则。&lt;/p&gt;

&lt;p&gt;方案由三个独立的工具组成&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semaphore-Wireguard：负责 Kubernetes 集群之间的流量加密；&lt;/li&gt;
&lt;li&gt;Semaphore-Service-Mirror：负责在无需外部负载均衡器参与的情况下，将一个集群中的服务暴露到另一个集群之中；&lt;/li&gt;
&lt;li&gt;Semaphore-Policy：负载在跨集群的 Pod 间通信里创建防火墙规则。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为了小型、轻量和尽可能的安全，这个组件是用 Go 编写的，并使用了 Kubernetes 和 Calico 的客户端。远端集群上的部署内容很小，本地控制器仅需要一系列的 Service Account，满足 Watch 资源的权限需求即可。&lt;/p&gt;

&lt;h2 id=&#34;路由和加密&#34;&gt;路由和加密&lt;/h2&gt;

&lt;p&gt;Semaphore-Wireguard 负责在不同集群的节点之间进行加密通信，并在本地主机上加入访问远端 Pod 子网的路由。每个集群的每个节点上都会运行一个 WireGuard 管理器，负责节点之间的点到点通信。它负责生成本地密钥并发现所有远端密钥和端点，并配置与所有远程节点的对等关系。此外，它还负责更新本地路由表，以便通过主机的 WireGuard 接口将所有流量导向远程 Pod 子网。因此，Pod 可以利用所有集群中的节点之间创建的 WireGuard 网状结构，触达远程集群上的 Pod。&lt;/p&gt;

&lt;p&gt;WireGuard 和 Calico 的集群内通信管理协作，形成了所有集群所有节点之间的网状结构，WireGuard 网络承担了节点之间的通信。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/utilitywarehouse/semaphore-wireguard/tree/main/deploy/example&#34; target=&#34;_blank&#34;&gt;Semaphore WireGuard 部署样例&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;下图表达了 WireGuard 形成的主机网格，其中的 &lt;code&gt;merit&lt;/code&gt; 就是我们的私有集群：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/semaphore-wireguard-full-mesh-example.png&#34; alt=&#34;semaphore-wireguard-full-mesh-example&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;service&#34;&gt;Service&lt;/h2&gt;

&lt;p&gt;Semaphore-Service-Mirror 是一个控制器，负责在不同 Kubernetes 集群之间复制服务。此处的镜像服务代表的是一个本地服务，其端点处于远端集群。&lt;/p&gt;

&lt;p&gt;镜像控制器会在本地集群创建服务，并用远程集群中 Pod 的地址来更新端点列表，最终形成一个 ClusterIP 类型的 Service。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/utilitywarehouse/semaphore-service-mirror/tree/master/deploy/example&#34; target=&#34;_blank&#34;&gt;控制器部署样例&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;例如 AWS 集群上有一个 Service 对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl --context=aws --namespace=sys-log get service fluentd
NAME      TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
fluentd   ClusterIP   10.3.88.18   &amp;lt;none&amp;gt;        8888/TCP,8889/TCP   164d
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;它包含的端点：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl --context=aws --namespace=sys-log get endpoints fluentd
NAME      ENDPOINTS                                                  AGE
fluentd   10.2.3.19:8889,10.2.4.19:8889,10.2.7.18:8889 + 3 more...   164d
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;镜像控制器会在 &lt;code&gt;semaphore-service-mirror&lt;/code&gt; 所在的命名空间 &lt;code&gt;sys-semaphore&lt;/code&gt; 中创建对应的 Service 和 Endpoint：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl --context=gcp --namespace=sys-semaphore get service | grep fluentd
aws-sys-log-73736d-fluentd   ClusterIP   10.5.184.192   &amp;lt;none&amp;gt;        8888/TCP,8889/TCP   25d

$ kubectl --context=gcp --namespace=sys-semaphore get endpoints | grep fluentd
aws-sys-log-73736d-fluentd   10.2.3.19:8889,10.2.4.19:8889,10.2.7.18:8889 + 3 more...   17d
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们会发现，这个 Service 指向是远程集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl --context=gcp --namespace=sys-semaphore describe service aws-sys-log-73736d-fluentd | grep Endpoints
Endpoints:         10.2.3.19:8888,10.2.4.19:8888,10.2.7.18:8888
Endpoints:         10.2.3.19:8889,10.2.4.19:8889,10.2.7.18:8889
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;控制器会监控远端资源，并根据事件进行更新，这样镜像服务就会及时地指向可用的端点。&lt;/p&gt;

&lt;p&gt;最后对 CoreDNS &lt;a href=&#34;https://github.com/utilitywarehouse/semaphore-service-mirror#coredns-config-example&#34; target=&#34;_blank&#34;&gt;进行配置&lt;/a&gt;，就能够更好地进行解析了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ drill fluentd.sys-log.svc.cluster.aws
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, rcode: NOERROR, id: 51067
;; flags: qr aa rd ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 
;; QUESTION SECTION:
;; fluentd.sys-log.svc.cluster.aws.     IN      A

;; ANSWER SECTION:
fluentd.sys-log.svc.cluster.aws.        5       IN      A       10.5.184.192
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样我们的 Pod 在无需关注镜像细节的情况下使用友好的服务名称了。&lt;/p&gt;

&lt;h2 id=&#34;policy&#34;&gt;Policy&lt;/h2&gt;

&lt;p&gt;Semaphore-Policy 是一个用于创建防火墙策略的组件，用于限制来自远端集群的访问。这个组件会创建用于 Calico 网络策略的 IP 组，来定义允许发起的流量。控制器的唯一任务就是根据标签选择器来监控远端集群的 Pod，然后根据监控结果在本地创建 NetworkSets。接下来，可以使用简单的标签来描述 Calico Network Policy，方便地实现跨集群防火墙规则。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/utilitywarehouse/semaphore-policy/tree/main/deploy&#34; target=&#34;_blank&#34;&gt;策略控制器部署样例&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;假设 GCP 集群中存在如下部署：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl --context=gcp --namespace=sys-log get po -o wide -l policy.semaphore.uw.io/name=forwarder
NAME              READY   STATUS    RESTARTS   AGE     IP          NODE                                      NOMINATED NODE   READINESS GATES
forwarder-4jdm6   1/1     Running   0          3d20h   10.4.1.3    worker-k8s-exp-1-4l87.c.uw-dev.internal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
forwarder-6ztl4   1/1     Running   0          3d20h   10.4.0.13   worker-k8s-exp-1-2868.c.uw-dev.internal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
forwarder-klxdc   1/1     Running   0          4h27m   10.4.4.2    master-k8s-exp-1-j5f8.c.uw-dev.internal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
forwarder-m9k27   1/1     Running   0          4h27m   10.4.5.2    master-k8s-exp-1-fc0b.c.uw-dev.internal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
forwarder-n6nsn   1/1     Running   0          4h27m   10.4.3.3    master-k8s-exp-1-31rv.c.uw-dev.internal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
forwarder-n8vnj   1/1     Running   0          3d20h   10.4.2.4    worker-k8s-exp-1-mdd7.c.uw-dev.internal   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这是一个 &lt;code&gt;sys-log&lt;/code&gt; 命名空间里名为 &lt;code&gt;forwarder&lt;/code&gt; 的 DaemonSet。为了让控制器在远程集群中创建所需资源，需要给这个 Daemonset 中的 Pod 加入标签 &lt;code&gt;policy.semaphore.uw.io/name=forwarder&lt;/code&gt;。加入标签之后，AWS 集群中的控制器就会创建对应的 &lt;code&gt;GlobalNetworkSet&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl --context=aws describe GlobalNetworkSet gcp-sys-log-forwarder
Name:         gcp-sys-log-forwarder
Namespace:
Labels:       managed-by=semaphore-policy
              policy.semaphore.uw.io/cluster=gcp
              policy.semaphore.uw.io/name=forwarder
              policy.semaphore.uw.io/namespace=sys-log
Annotations:  projectcalico.org/metadata: {&amp;quot;uid&amp;quot;:&amp;quot;c7569765-a47d-424c-9533-80e4a7c201d6&amp;quot;,&amp;quot;creationTimestamp&amp;quot;:&amp;quot;2021-04-09T15:04:43Z&amp;quot;}
API Version:  crd.projectcalico.org/v1
Kind:         GlobalNetworkSet
Spec:
  Nets:
    10.4.5.2/32
    10.4.4.2/32
    10.4.1.3/32
    10.4.0.13/32
    10.4.3.3/32
    10.4.2.4/32
Events:  &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个组合包含了远端对象的 IP 地址，可以用于 Calico 的网络策略，定义允许进入的流量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  name: allow-to-fluentd
spec:
  selector: app.kubernetes.io/name == &#39;fluentd&#39;
  types:
    - Ingress
  ingress:
    - action: Allow
      protocol: TCP
      source:
        selector: &amp;gt;-
          policy.semaphore.uw.io/name == &#39;forwarder&#39; &amp;amp;&amp;amp;
          policy.semaphore.uw.io/namespace == &#39;sys-log&#39; &amp;amp;&amp;amp;
          policy.semaphore.uw.io/cluster == &#39;gcp&#39;
        namespaceSelector: global()
      destination:
        ports:
          - 8889
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上边的规则允许来自远端 &lt;code&gt;forwarder&lt;/code&gt; 到本地 &lt;code&gt;fluentd&lt;/code&gt; 的访问。&lt;/p&gt;

&lt;h2 id=&#34;后记&#34;&gt;后记&lt;/h2&gt;

&lt;p&gt;这套东西对我们来说很有用，但是它的局限性也是显而易见的——如果恰巧适用于读者的环境和需求，那自然很好；如果不是，也希望读者在本文中得到一点启发。&lt;/p&gt;

&lt;p&gt;没有 Calico 和 WireGuard，这个方案可能就难于落地了，方案的复杂部分主要是由这两个项目完成的，也正因为此，我们对我们的方案充满信心，这里对他们致以崇高敬意。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Semaphore WireGuard：&lt;code&gt;https://github.com/utilitywarehouse/semaphore-wireguard&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Semaphore WireGuard 部署示例：&lt;code&gt;https://github.com/utilitywarehouse/semaphore-wireguard/tree/main/deploy/example&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Semaphore Service Mirror：&lt;code&gt;https://github.com/utilitywarehouse/semaphore-service-mirror&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Semaphore Service Mirror 部署示例：&lt;code&gt;https://github.com/utilitywarehouse/semaphore-service-mirror/tree/master/deploy/example&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Semaphore Policy：&lt;code&gt;https://github.com/utilitywarehouse/semaphore-policy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Semaphore Policy 部署示例：&lt;code&gt;https://github.com/utilitywarehouse/semaphore-policy/tree/main/deploy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>用 Volcano 填补私有集群的空闲时间</title>
      <link>/post/volcano-mining/</link>
      <pubDate>Tue, 07 Sep 2021 18:41:55 +0800</pubDate>
      <guid>/post/volcano-mining/</guid>
      <description>

&lt;h2 id=&#34;引子&#34;&gt;引子&lt;/h2&gt;

&lt;p&gt;在私有 Kubernetes 场景下，因为硬件规模是一定的，不太会随着业务高峰低谷进行裁撤，因此缩减下来的服务资源并不能带来成本上的优势，如果在闲时~&lt;del&gt;挖挖矿&lt;/del&gt;~跑跑 AI 大数据什么的是不是可以贴补一下家用呢？Volcano 值得一试。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://volcano.sh/zh/&#34; target=&#34;_blank&#34;&gt;Volcano（https://volcano.sh/zh/）&lt;/a&gt; 是“一个用于高性能工作负载场景下基于Kubernetes 的容器批量调度引擎”，项目主页上罗列了为数众多的应用案例和适配的 AI、大数据平台。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;它提供了Kubernetes目前缺少的一套机制，这些机制通常是许多高性能 工作负载所必需的，包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;机器学习/深度学习&lt;/li&gt;
&lt;li&gt;生物学计算/基因计算&lt;/li&gt;
&lt;li&gt;大数据应用
这些类型的应用程序通常运行在像 Tensorflow、Spark、PyTorch、 MPI 等通用领域框架上，Volcano 无缝对接这些框架。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;假设这样一种场景，忙时会占用集群 60% 的 CPU 资源，而闲时则只需要一半，我们需要这样一个能力：&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;使用 Volcano，简单的几个 YAML 就能完成任务。&lt;/p&gt;

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

&lt;p&gt;可以用 YAML 直接部署 Volcano，K8s 版本要求在 1.13-1.21 之间（1.22 因为 CRD 资源组升级为 v1，需要进行一些修改才能部署），部署文件如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;x86_64&lt;/strong&gt;：&lt;code&gt;https://raw.githubusercontent.com/volcano-sh/volcano/master/installer/volcano-development.yaml&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;arm64&lt;/strong&gt;：&lt;code&gt;https://raw.githubusercontent.com/volcano-sh/volcano/master/installer/volcano-development-arm64.yaml&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;部署之后，会生成若干 CRD，以及一个 &lt;code&gt;volcano-system&lt;/code&gt; 命名空间，其中包含 Volcano 的工作负载。&lt;/p&gt;

&lt;h2 id=&#34;启动工作负载&#34;&gt;启动工作负载&lt;/h2&gt;

&lt;p&gt;我用了一个单节点 Kubernetes 集群，剩余可用资源刚好 1 CPU：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl describe node single | grep -E &amp;quot;^\s+cpu\s+&amp;quot;
  cpu                1 (50%)     0 (0%)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建一个 Deployment，为方便控制，我们使用两个占用 300m CPU 的实例（源码见附录 &lt;code&gt;workload.yaml&lt;/code&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl apply -f workload.yaml
deployment.apps/sleep created
$ kubectl describe node single | grep -E &amp;quot;^\s+cpu\s+&amp;quot;
  cpu                1600m (80%)  600m (30%)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，CPU Request 已经上升至 80%。&lt;/p&gt;

&lt;h2 id=&#34;创建-volcano-队列&#34;&gt;创建 Volcano 队列&lt;/h2&gt;

&lt;p&gt;源码见附录 &lt;code&gt;queue.yaml&lt;/code&gt;，创建之后查看其状态：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f queue.yaml
queue.scheduling.volcano.sh/q800 created
$ kubectl describe queue q800
...
Spec:
  Capability:
    Cpu:        800m
  Reclaimable:  false
  Weight:       1
Status:
  State:  Open
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面结果表明，创建了一个 CPU 容量为 &lt;code&gt;800m&lt;/code&gt; 且状态开放的队列。&lt;/p&gt;

&lt;h2 id=&#34;创建-valcano-任务&#34;&gt;创建 Valcano 任务&lt;/h2&gt;

&lt;p&gt;在创建任务之前，我们首先要给 Volcano 和工作负载分别创建一个 PriorityClass：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: scheduling.k8s.io/v1
description: Used for volcano jobs
kind: PriorityClass
metadata:
  name: volcano-task
preemptionPolicy: PreemptLowerPriority
value: -1
---
apiVersion: scheduling.k8s.io/v1
description: Used for workloads
kind: PriorityClass
metadata:
  name: workload-task
preemptionPolicy: PreemptLowerPriority
value: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;没有标明 &lt;code&gt;PriorityClass&lt;/code&gt; 的 Pod ，其缺省优先级为 0，所以使用 -1 优先级的 Pod 就属于人见人踩的小角色了。而 &lt;code&gt;workload-task&lt;/code&gt; 声明的 &lt;code&gt;PreemptLowerPriority&lt;/code&gt; 策略，会在调度过程中抢占低优先级任务的资源。&lt;/p&gt;

&lt;p&gt;接下来定义一个 Volcano 任务，源码见 &lt;code&gt;job1.yaml&lt;/code&gt;，定义其优先级为刚刚创建的 &lt;code&gt;volcano-task&lt;/code&gt;。这个 Job 配置最小可用副本为 2，和 Deployment 的定义类似，必须有两个 Pod 全部启动。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl apply -f job.yaml
job.batch.volcano.sh/jobb created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看各种对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ vcctl job list
Name   Creation       Phase       JobType     Replicas    Min   Pending   Running   Succeeded   Failed    Unknown     RetryCount
jobb   2021-09-09     Pending     Batch       2           2     2         0         0           0         0           0
$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
jobb-sleep-0             0/1     Pending   0          7s
jobb-sleep-1             0/1     Pending   0          7s
sleep-6458fc8f96-qr6s6   1/1     Running   0          14m
sleep-6458fc8f96-tmf9k   1/1     Running   0          14m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到任务和 Pod 都处于 Pending 状态，查看 Pod 的状态，会发现 Pending 原因是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl describe po jobb-sleep-0
...
Warning  FailedScheduling  8m22s  volcano  all nodes are unavailable: 1 node(s) resource fit failed.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为资源不足，导致任务被挂起，这是我们期待的效果。&lt;/p&gt;

&lt;h2 id=&#34;工作负载扩缩容&#34;&gt;工作负载扩缩容&lt;/h2&gt;

&lt;p&gt;假设闲时工作负载资源需求降低 50%，看看会发生什么：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl scale deployment sleep --replicas=1
deployment.apps/sleep scaled
$ vcctl job list
Name   Creation       Phase       JobType     Replicas    Min   Pending   Running   Succeeded   Failed    Unknown     RetryCount
jobb   2021-09-09     Running     Batch       2           2     0         2         0           0         0           0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;此时看到队列中的 Job 已经启动运行。&lt;/p&gt;

&lt;p&gt;如果工作负载开始增加，优先级就会发生作用了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;dustise:mine/ $ kubectl scale deployment sleep --replicas=2                                                               [15:15:25]
deployment.apps/sleep scaled
dustise:mine/ $ kubectl get pods                                                                                          [15:15:29]
NAME                     READY   STATUS        RESTARTS   AGE
jobb-sleep-0             0/1     Pending   0          4m17s
jobb-sleep-1             0/1     Pending   0          4m17s
sleep-7f67d6c47c-5cp7s   1/1     Running   0          5m12s
sleep-7f67d6c47c-jrsqh   1/1     Running   0          4m29s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到 &lt;code&gt;jobb&lt;/code&gt; 已经退回 Pending 状态，任务回到排队状态。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ vcctl queue list
Name                     Weight  State   Inqueue Pending Running
...
q800                     1       Open    1       0       0       0
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;后记&#34;&gt;后记&lt;/h2&gt;

&lt;p&gt;也不知道 Volcano 啥时候弄个挖矿调度器。。&lt;/p&gt;

&lt;h2 id=&#34;源码&#34;&gt;源码&lt;/h2&gt;

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

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: sleep
  name: sleep
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      containers:
      - image: dustise/sleep:v0.9.8
        name: sleep
        resources:
          requests:
            cpu: 300m
          limits:
            cpu: 300m
&lt;/code&gt;&lt;/pre&gt;

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

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
  name: jobb
spec:
  minAvailable: 2
  schedulerName: volcano
  queue: q800
  priorityClassName: volcano-task
  policies:
    - event: PodEvicted
      action: RestartJob
  tasks:
    - replicas: 2
      name: sleep
      policies:
      - event: TaskCompleted
        action: CompleteJob
      template:
        spec:
          containers:
          - image: dustise/sleep:v0.9.8
            name: sleep
            resources:
              requests:
                cpu: 300m
              limits:
                cpu: 300m
            restartPolicy: Never
&lt;/code&gt;&lt;/pre&gt;

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

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
  name: q800
spec:
  weight: 1
  reclaimable: false
  capability:
    cpu: 800m
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>（空想场景）使用 Prometheus 监控特定日志行数</title>
      <link>/post/line-count-to-promethes/</link>
      <pubDate>Wed, 11 Aug 2021 20:06:06 +0800</pubDate>
      <guid>/post/line-count-to-promethes/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;感谢 @云原生小白 提供线索&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在系统的监控过程中，有时我们只是想要知道一些特定内容的出现数量或者频度，并不关心他的具体内容，而且也不想特意部署一个 Loki 或者 Elasticsearch，这时就可以使用 Fluentd 花里胡哨的插件功能来完成任务了。&lt;/p&gt;

&lt;p&gt;Fluentd 有一个 &lt;a href=&#34;https://docs.fluentd.org/monitoring-fluentd/monitoring-prometheus&#34; target=&#34;_blank&#34;&gt;Prometheus 插件&lt;/a&gt;，能够提供 Prometheus 接口提供采集数据，插件需要用 &lt;code&gt;fluent-gem&lt;/code&gt; 进行安装，如果在 Docker 中的话，可以使用下列 Dockerfile：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM fluentd:v1.9.1-1.0
USER root
RUN fluent-gem install fluent-plugin-prometheus
USER fluent
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个插件的基本配置方式是，提供一个 &lt;code&gt;promethues&lt;/code&gt; 的类型，包含一个 &lt;code&gt;&amp;lt;metric&amp;gt;&lt;/code&gt; 元素用于对指标结构进行定义。例如文档中使用的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;  @type prometheus
  &amp;lt;metric&amp;gt;
    name fluentd_input_status_num_records_total
    type counter
    desc The total number of incoming records
    &amp;lt;labels&amp;gt;
      tag ${tag}
      hostname ${hostname}
    &amp;lt;/labels&amp;gt;
  &amp;lt;/metric&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这种指标放在 &lt;code&gt;&amp;lt;filter&amp;gt;&lt;/code&gt; 用于指示输入数量，而放在 &lt;code&gt;&amp;lt;match&amp;gt;&lt;/code&gt; 中则可以监控输出数量。&lt;/p&gt;

&lt;p&gt;这里定义了一个名为 &lt;code&gt;fluentd_input_status_num_records_total&lt;/code&gt; 的指标，其类型为 &lt;code&gt;counter&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;定义指标之后，还要将其暴露给 Prometheus：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;&amp;lt;source&amp;gt;
  @type prometheus
  bind 0.0.0.0
  port 24231
  metrics_path /metrics
&amp;lt;/source&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段配置定义了一个监听 &lt;code&gt;24231&lt;/code&gt; 端口的 Prometheus 端点，路径为 &lt;code&gt;/metrics&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;举个栗子&#34;&gt;举个栗子&lt;/h2&gt;

&lt;p&gt;接下来用一个完整场景来展示这个例子，假设我们要监控 &lt;code&gt;/logs/input.txt&lt;/code&gt; 中的 &lt;code&gt;warning&lt;/code&gt; 数量，会采用文末的完整配置，分段解释如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; 段定义采集文件名称&lt;/li&gt;
&lt;li&gt;第一个 &lt;code&gt;&amp;lt;filter&amp;gt;&lt;/code&gt; 中使用 &lt;code&gt;@type promethues&lt;/code&gt; 来监控输入数量，生成指标 &lt;code&gt;fluentd_input_status_num_records_total&lt;/code&gt;，类型为 &lt;code&gt;counter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;第二个 &lt;code&gt;&amp;lt;filter&amp;gt;&lt;/code&gt; 用 &lt;code&gt;@type grep&lt;/code&gt; 的正则表达式插件对输入进行过滤&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;match&amp;gt;&lt;/code&gt; 节中使用 &lt;code&gt;@type copy&lt;/code&gt; 对输出进行分流&lt;/li&gt;
&lt;li&gt;第一个 &lt;code&gt;&amp;lt;store&amp;gt;&lt;/code&gt; 输出 &lt;code&gt;fluentd_output_status_num_records_total&lt;/code&gt; 的 Promethues 指标，对过滤出来的文本进行计数&lt;/li&gt;
&lt;li&gt;第二个 &lt;code&gt;&amp;lt;store&amp;gt;&lt;/code&gt; 将输出内容展示在 &lt;code&gt;stdout&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;配置结束之后启动采集过程，可以使用类似如下脚本：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
docker run -it --rm \
        -v $(pwd)/etc:/etc/fluentd \
        -v $(pwd)/log:/data \
        -p 12345:12345 \
        fluentd:prom \
        fluentd -c /etc/fluentd/fluentd.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;启动之后，我们向日志中输出内容，例如 &lt;code&gt;echo &amp;quot;warn&amp;quot; &amp;gt;&amp;gt; input.txt&lt;/code&gt;，会看到 &lt;code&gt;fluentd&lt;/code&gt; 日志输出了类似 &lt;code&gt;2021-08-14 07:06:55.688191458 +0000 custom.log: {&amp;quot;message&amp;quot;:&amp;quot;warn&amp;quot;}&lt;/code&gt; 的内容，如果使用 &lt;code&gt;curl&lt;/code&gt; 访问开放出来的 &lt;code&gt;:12345/metrics&lt;/code&gt;，会看到输出中的如下内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;fluentd_input_status_num_records_total{tag=&amp;quot;custom.log&amp;quot;,hostname=&amp;quot;757214c8a91a&amp;quot;} 2.0      │➜  log  vim fluentd.conf
fluentd_output_status_num_records_total{tag=&amp;quot;custom.log&amp;quot;,hostname=&amp;quot;757214c8a91a&amp;quot;} 1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这是很常见的指标格式，如果在 Kubernetes 中，对 Pod 进行注解，纳入采集范围，就可以像其它监控指标一样使用了。&lt;/p&gt;

&lt;h2 id=&#34;fluentd-conf&#34;&gt;fluentd.conf&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;&amp;lt;source&amp;gt;
  @type tail
  path /data/input.txt
  pos_file /data/input.pos
  tag custom.log
  &amp;lt;parse&amp;gt;
    @type none
  &amp;lt;/parse&amp;gt;
&amp;lt;/source&amp;gt;
&amp;lt;filter custom.**&amp;gt;
  @type prometheus
  &amp;lt;metric&amp;gt;
    name fluentd_input_status_num_records_total
    type counter
    desc The total number of incoming records
    &amp;lt;labels&amp;gt;
      tag ${tag}
      hostname ${hostname}
    &amp;lt;/labels&amp;gt;
  &amp;lt;/metric&amp;gt;
&amp;lt;/filter&amp;gt;
&amp;lt;filter custom.**&amp;gt;
  @type grep
  &amp;lt;regexp&amp;gt;
    key message
    pattern /warn/
  &amp;lt;/regexp&amp;gt;
&amp;lt;/filter&amp;gt;
&amp;lt;match custom.**&amp;gt;
  @type copy
  &amp;lt;store&amp;gt;
    @type prometheus
    &amp;lt;metric&amp;gt;
      name fluentd_output_status_num_records_total
      type counter
      desc The total number of outgoing records
      &amp;lt;labels&amp;gt;
        tag ${tag}
        hostname ${hostname}
      &amp;lt;/labels&amp;gt;
    &amp;lt;/metric&amp;gt;
  &amp;lt;/store&amp;gt;
  &amp;lt;store&amp;gt;
    @type stdout
&amp;lt;/match&amp;gt;

&amp;lt;source&amp;gt;
  @type prometheus
  bind 0.0.0.0
  port 12345
  metrics_path /metrics
&amp;lt;/source&amp;gt;

&amp;lt;source&amp;gt;
  @type prometheus_output_monitor
  interval 10
  &amp;lt;labels&amp;gt;
    hostname ${hostname}
  &amp;lt;/labels&amp;gt;
&amp;lt;/source&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>借助 Shell Operator 监控集群中的镜像漏洞</title>
      <link>/post/monitoring-vulnerabilities/</link>
      <pubDate>Tue, 30 Mar 2021 22:14:50 +0800</pubDate>
      <guid>/post/monitoring-vulnerabilities/</guid>
      <description>&lt;p&gt;在&lt;a href=&#34;https://blog.fleeto.us/post/copy-labels-from-node-to-pod-by-shell-operator/&#34; target=&#34;_blank&#34;&gt;使用 Shell-Operator，让 Pod 继承节点标签&lt;/a&gt;一文中，展示了使用 &lt;a href=&#34;https://github.com/flant/shell-operator&#34; target=&#34;_blank&#34;&gt;Shell Operator&lt;/a&gt; 在创建工作负载时从对应节点拷贝指定标签的方法。Shell Operator 还有个功能就是生成 Prometheus Metrics，本文就是从这个角度出发，用 Shell Operator 定时调用 &lt;a href=&#34;https://github.com/aquasecurity/trivy&#34; target=&#34;_blank&#34;&gt;Trivy&lt;/a&gt; 扫描指定命名空间下的所有在用镜像，并输出指标给 Prometheus，最终可以进行展示和告警。&lt;/p&gt;

&lt;p&gt;Shell Operator 除了上次提到的根据 Kubernetes 事件进行触发之外，还包含了在启动时和定期运行两种触发方式，这里我们用定时触发的方式：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;configVersion: v1
schedule:
- name: &amp;quot;scan images each 5 min&amp;quot;
  crontab: &amp;quot;*/5 * * * *&amp;quot;
  allowFailure: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里我定义了一个 5 分钟触发一次的任务。镜像接受一个缺省为 &lt;code&gt;NS_LABEL=&amp;quot;trivy=true&amp;quot;&lt;/code&gt; 的标签，每次触发时，会根据这个标签选择命名空间，获取选定命名空间中运行的镜像，并调用 &lt;code&gt;trivy i -f json [image]&lt;/code&gt; 输出结果，将结果转换为 Prometheus 指标进行输出。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM flant/shell-operator:v1.0.0-rc.2
ENV TRIVY_CACHE_DIR=/data/trivy \
    TRIVY_QUIET=true \
    GITHUB_TOKEN=&amp;quot;&amp;quot; \
    NS_LABEL=&amp;quot;trivy=true&amp;quot;
RUN apk --no-cache add python3=3.8.8-r0
COPY trivy-scanner.py /hooks
COPY trivy /usr/local/bin
VOLUME [ &amp;quot;/data/trivy&amp;quot;, &amp;quot;/data/cache&amp;quot; ]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上文中的几个环境变量：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TRIVY_CACHE_DIR：指定 Trivy 缓存目录，可以在 Kubernetes 中把 Trivy 工作目录持久化。&lt;/li&gt;
&lt;li&gt;TRIVY_QUIET：Trivy 环境变量，指定 Trivy 用静默方式运行。&lt;/li&gt;
&lt;li&gt;GITHUB_TOKEN：Trivy 会使用该 Token 更新漏洞库，防止触发 Rate Limit。&lt;/li&gt;
&lt;li&gt;NS_LABLE：仅对符合标签的命名空间进行扫描。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在脚本中执行的任务还是很简单的三部曲：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kubectl get ns&lt;/li&gt;
&lt;li&gt;kubectl get po&lt;/li&gt;
&lt;li&gt;trivy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以看到这里需要列表查看 Namespace 和 Pod 对象，因此我们的 RBAC 需要进行相应的配置，例如设置 ClusterRole：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: trivy-scanner
rules:
- resources:
  - pods
  - namespaces
  verbs:
  - get
  - watch
  - list
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;实际上也可以通过之前用到的 Kubernetes 事件进行触发。因此这里还保留了 watch 事件。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Deployment 需要注意一点是，Shell Operator 使用 9115 端口的 &lt;code&gt;/metrics&lt;/code&gt; 开放指标数据供 Prometheus 抓取，因此需要使用 &lt;code&gt;annotations&lt;/code&gt; 对此进行标注。&lt;/p&gt;

&lt;p&gt;使用 Trivy 获取到 JSON 格式出具的扫描结果之后，就可以利用 Shell Operator 的指标输出能力，把数据整理为如下格式：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&amp;quot;name&amp;quot;:&amp;quot;so_vulnerabilities&amp;quot;,&amp;quot;set&amp;quot;:0,&amp;quot;labels&amp;quot;:{&amp;quot;image&amp;quot;:&amp;quot;dustise/sleep:v0.9.6&amp;quot;,&amp;quot;severity&amp;quot;:&amp;quot;UNKNOWN&amp;quot;}}
{&amp;quot;name&amp;quot;:&amp;quot;so_vulnerabilities&amp;quot;,&amp;quot;set&amp;quot;:1,&amp;quot;labels&amp;quot;:{&amp;quot;image&amp;quot;:&amp;quot;dustise/sleep:v0.9.6&amp;quot;,&amp;quot;severity&amp;quot;:&amp;quot;HIGH&amp;quot;}}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;每行一条记录，输出到 &lt;code&gt;$METRICS_PATH&lt;/code&gt; 环境变量指定的文本文件中，Shell Operator 会把它转换为 Prometheus 指标：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;so_vulnerabilities{hook=&amp;quot;trivy-scanner.py&amp;quot;,image=&amp;quot;dustise/sleep:v0.9.6&amp;quot;,severity=&amp;quot;CRITICAL&amp;quot;} 1
so_vulnerabilities{hook=&amp;quot;trivy-scanner.py&amp;quot;,image=&amp;quot;dustise/sleep:v0.9.6&amp;quot;,severity=&amp;quot;HIGH&amp;quot;} 11
so_vulnerabilities{hook=&amp;quot;trivy-scanner.py&amp;quot;,image=&amp;quot;dustise/sleep:v0.9.6&amp;quot;,severity=&amp;quot;LOW&amp;quot;} 2
so_vulnerabilities{hook=&amp;quot;trivy-scanner.py&amp;quot;,image=&amp;quot;dustise/sleep:v0.9.6&amp;quot;,severity=&amp;quot;MEDIUM&amp;quot;} 9
so_vulnerabilities{hook=&amp;quot;trivy-scanner.py&amp;quot;,image=&amp;quot;dustise/sleep:v0.9.6&amp;quot;,severity=&amp;quot;UNKNOWN&amp;quot;} 0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来就可以在 Prometheus 中检查输出内容，出具监控图表或者制定告警规则了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 Kubernetes 环境中检查镜像签名的一种方法</title>
      <link>/post/that-is-my-image/</link>
      <pubDate>Sat, 20 Mar 2021 00:19:56 +0800</pubDate>
      <guid>/post/that-is-my-image/</guid>
      <description>

&lt;p&gt;Kubernetes 的供应链安全需求中，有一个重要的镜像签署和校验的环节，这个环节可以使用 &lt;a href=&#34;https://blog.fleeto.us/post/k8s-notary-and-opa/&#34; target=&#34;_blank&#34;&gt;OPA 结合 Notary&lt;/a&gt; 的方式来完成。最近 &lt;a href=&#34;https://training.linuxfoundation.cn/news/208&#34; target=&#34;_blank&#34;&gt;Linux基金会宣布免费 sigstore 签名服务，以确认软件的来源和真实性&lt;/a&gt;，在&lt;a href=&#34;https://sigstore.dev/&#34; target=&#34;_blank&#34;&gt;项目网站&lt;/a&gt;闲逛时，发现一个叫做 &lt;a href=&#34;https://github.com/sigstore/cosign&#34; target=&#34;_blank&#34;&gt;cosign&lt;/a&gt; 的子项目，这是个轻量级的选择，让我非常有兴趣，于是就有了本文。&lt;/p&gt;

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

&lt;p&gt;目前这个工具还没有提供二进制发布，需要克隆源代码，并使用 &lt;strong&gt;go 1.5&lt;/strong&gt; 进行构建，具体方法请参阅项目页面。简单说就是&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# git clone https://github.com/sigstore/cosign.git
...
# cd cosign
# go build -o cosign ./cmd/cosign
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个工具的最基础功能有三个，分别是生成密钥对、镜像签名和校验签名。&lt;/p&gt;

&lt;h2 id=&#34;生成密钥对&#34;&gt;生成密钥对&lt;/h2&gt;

&lt;p&gt;这个功能是很直白的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;cosign generate-key-pair
Enter password for private key:
Enter again:
Private key written to cosign.key
Public key written to cosign.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;执行命令之后，输入密码，就会生成密钥对文件，私钥和公钥分别是 &lt;code&gt;consign.key&lt;/code&gt; 和 &lt;code&gt;cosign.pub&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;签名&#34;&gt;签名&lt;/h2&gt;

&lt;p&gt;可以使用前边生成的密钥对进行签名，例如我的工具镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;cosign sign -key cosign.key dustise/sleep:v0.9.6
Enter password for private key:
Pushing signature to: index.docker.io/dustise/sleep:sha256-92dad62e00d08157a3921b7d7b568a247a8b24e8a067ad5dc20b210d7b1c2ad1.cosign
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;读者需要注意的一点是，这个功能是对&lt;strong&gt;仓库中镜像的哈希码&lt;/strong&gt;生效的，因此签署过程无需本地镜像的参与，cosign 会直接在镜像仓库中获取对应 tag 的 sha256 内容，签署之后生成一个 OCI 镜像推送到该镜像的原有仓库之中，例如前面为 &lt;code&gt;dustise/sleep:v0.9.6&lt;/code&gt; 进行签名，就生成了一个 &lt;code&gt;dustise/sleep:sha256-92da.....1c2ad1.cosign&lt;/code&gt; 的镜像。如果被签名镜像在本地不存在，在完成操作之后，使用 &lt;code&gt;docker images&lt;/code&gt; 命令查看，会发现被签署镜像和签署生成的镜像都不存在于本地。&lt;/p&gt;

&lt;p&gt;另外一个就是，因为这里有 Push 操作，因此这个签署过程通常是有登录镜像库的需求的。&lt;/p&gt;

&lt;h2 id=&#34;校验&#34;&gt;校验&lt;/h2&gt;

&lt;p&gt;校验过程很简单，使用 &lt;code&gt;verify&lt;/code&gt; 指令，指定公钥即可，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;cosign verify  -key cosign.pub dustise/sleep:v0.9.6
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
  - Any certificates were verified against the Fulcio roots.
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;注意&#34;&gt;注意&lt;/h2&gt;

&lt;p&gt;如果使用 cosign 来进行签署，过程基本上来说还算是愉快的，私钥放置在 CI 之中，而公钥则可以保存在集群里，简单一点的方式，使用客户端定期扫描；复杂的方式，可以实现一个简单的 admission controller 来根据 Selector 对负载进行校验，同样需要注意的是，cosign 只针对远程（镜像库）进行操作，对本地的同 Tag 替换是没什么防御力的，因此这里还要使用 Always Pull 的策略进行弥补（可以使用 Kyverno 或者 Gatekeeper 来强制实施）。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：KubeNurse——集群网络监控</title>
      <link>/post/cn-toolkit-kubenurse/</link>
      <pubDate>Mon, 01 Mar 2021 15:57:30 +0800</pubDate>
      <guid>/post/cn-toolkit-kubenurse/</guid>
      <description>

&lt;h2 id=&#34;地址&#34;&gt;地址&lt;/h2&gt;

&lt;p&gt;Kubenurse：&lt;code&gt;https://github.com/postfinance/kubenurse&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;在 Kubernetes 集群运行中，一个常见故障就是集群内网络故障，经常会因为临时策略变更或者网络抖动导致一些古怪问题，而实际场景里的虚拟机和网络的监控经常是由其它部门管理的，如果从业务和 Kubernetes 这样的上层设施着手，可能需要一些时间才能解决问题。&lt;code&gt;kubenurse&lt;/code&gt; 项目使用 HTTP 检测的方式提供了常用的几个监控指标。&lt;/p&gt;

&lt;p&gt;这个工具的实现也很直接，用 Daemonset 的形式部署在每个集群节点上，每个 Pod 都会通过 HTTP 检测的方式对上述几种目标分别进行访问，最后用 Prometheus Summary 指标的形式暴露出来用于监控。检测机制如图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/nurse.png&#34; alt=&#34;nurse&#34; /&gt;&lt;/p&gt;

&lt;p&gt;每个 Pod 都开放了 8080 的 http 端口，包含以下端点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; 或者 &lt;code&gt;/alive&lt;/code&gt;：返回本节点信息&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/alwayshappy&lt;/code&gt;：返回 HTTP 200 用于心跳&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/metrics&lt;/code&gt;：暴露 Prometheus 指标数据&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;/alive&lt;/code&gt; 返回的节点信息如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;api_server_direct&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;api_server_dns&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;me_ingress&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;me_service&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;hostname&amp;quot;: &amp;quot;kubenurse-1234-x2bwx&amp;quot;,
  &amp;quot;neighbourhood_state&amp;quot;: &amp;quot;ok&amp;quot;,
  &amp;quot;neighbourhood&amp;quot;: [
   {
    &amp;quot;PodName&amp;quot;: &amp;quot;kubenurse-1234-8fh2x&amp;quot;,
    &amp;quot;PodIP&amp;quot;: &amp;quot;10.10.10.67&amp;quot;,
    &amp;quot;HostIP&amp;quot;: &amp;quot;10.12.12.66&amp;quot;,
    &amp;quot;NodeName&amp;quot;: &amp;quot;k8s-66.example.com&amp;quot;,
    &amp;quot;Phase&amp;quot;: &amp;quot;Running&amp;quot;
   },
   {
    &amp;quot;PodName&amp;quot;: &amp;quot;kubenurse-1234-ffjbs&amp;quot;,
    &amp;quot;PodIP&amp;quot;: &amp;quot;10.10.10.138&amp;quot;,
    &amp;quot;HostIP&amp;quot;: &amp;quot;10.12.12.89&amp;quot;,
    &amp;quot;NodeName&amp;quot;: &amp;quot;k8s-89.example.com&amp;quot;,
    &amp;quot;Phase&amp;quot;: &amp;quot;Running&amp;quot;
   }
  ],
  &amp;quot;headers&amp;quot;: {
   &amp;quot;Accept&amp;quot;: [
    &amp;quot;text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8&amp;quot;
   ],
   &amp;quot;Accept-Encoding&amp;quot;: [
    &amp;quot;gzip, deflate, br&amp;quot;
   ],
   ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，其中包含了上述所说的几个检测结果。&lt;/p&gt;

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

&lt;p&gt;源码中包含了一个 &lt;code&gt;example&lt;/code&gt; 目录，简单地 &lt;code&gt;kubectl apply&lt;/code&gt; 就可以完成部署，这里有两个可能需要修改的地方：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;缺省命名空间是 &lt;code&gt;kube-system&lt;/code&gt;，建议查找替换，并要注意调整 RBAC 授权。&lt;/li&gt;
&lt;li&gt;涉及 Ingress 检测，因此要注意提供正确的域名。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;监控&#34;&gt;监控&lt;/h2&gt;

&lt;p&gt;部署成功之后，Prometheus 会根据 Daemonset 中的注解采集数据：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
      annotations:
        prometheus.io/path: &amp;quot;/metrics&amp;quot;
        prometheus.io/port: &amp;quot;8080&amp;quot;
        prometheus.io/scheme: &amp;quot;http&amp;quot;
        prometheus.io/scrape: &amp;quot;true&amp;quot;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;访问任意 Pod 的 &lt;code&gt;:8080/metrics&lt;/code&gt; 端点，会看到如下指标：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubenurse_errors&lt;/code&gt;：如果检测过程中出现错误，这个计数器会进行累加。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubenurse_request&lt;/code&gt;：一个 Summary 类型的指标，正常检测结果的时间消耗分布。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这两个指标使用 &lt;code&gt;type&lt;/code&gt; 标签对结果进行标识，对应几种不同的检测目标：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;api_server_direct&lt;/code&gt;：从节点直接检测 API Server&lt;/li&gt;
&lt;li&gt;&lt;code&gt;api_server_dns&lt;/code&gt;：从节点通过 DNS 检测 API Server&lt;/li&gt;
&lt;li&gt;&lt;code&gt;me_ingress&lt;/code&gt;：通过 Ingress 检测本服务 Service&lt;/li&gt;
&lt;li&gt;&lt;code&gt;me_service&lt;/code&gt;：使用 Service 检测本服务 Service&lt;/li&gt;
&lt;li&gt;&lt;code&gt;path_$KUBELET_HOSTNAME&lt;/code&gt;：节点之间的互相检测&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如此一来，我们就可以根据各种延迟时间的分布情况，以及返回错误的数量来确认集群网络状况了。&lt;/p&gt;

&lt;h2 id=&#34;注意&#34;&gt;注意&lt;/h2&gt;

&lt;p&gt;节点较多时，每次采集可能会产生 &lt;code&gt;n*(n-1)&lt;/code&gt; 次访问，会造成较重负载，可以给 Pod 打标签，并使用标签过滤的方式来减少请求，但是这样一来，就会导致检测结果不够全面的问题，因此还需对实际应用进行权衡。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 策略引擎对比：OPA/Gatekeeper vs Kyverno</title>
      <link>/post/k8s-policy-comparison/</link>
      <pubDate>Tue, 23 Feb 2021 16:57:55 +0800</pubDate>
      <guid>/post/k8s-policy-comparison/</guid>
      <description>

&lt;p&gt;Kubernetes 的 &lt;a href=&#34;https://kubernetes.io/docs/concepts/policy/pod-security-policy/&#34; target=&#34;_blank&#34;&gt;Pod Security Policy（PSP）&lt;/a&gt;即将被&lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/97171&#34; target=&#34;_blank&#34;&gt;淘汰和移除&lt;/a&gt;，所以需要找到一个替代方案来填补这个即将出现的空白。目前看来，Kubernetes 自身并没有准备相应的替代方案，因此需要在 Kubernetes 之外寻求解决之道。CNCF 的两个头部项目可能会成为首选的替代产品，它们分别是基于 &lt;a href=&#34;https://www.openpolicyagent.org/&#34; target=&#34;_blank&#34;&gt;Open Policy Agent（OPA）&lt;/a&gt;的 &lt;a href=&#34;https://github.com/open-policy-agent/gatekeeper&#34; target=&#34;_blank&#34;&gt;Gatekeeper&lt;/a&gt; 以及 &lt;a href=&#34;https://kyverno.io/&#34; target=&#34;_blank&#34;&gt;Kyverno&lt;/a&gt;，两个产品各行有千秋，但是目前还没有对这两个产品进行过正式的比较，这就让面临选择的用户无从下手了。这两个项目都是全功能的 Kubernetes 策略引擎，因此其功能不仅限于替代 PSP。本文尝试对 Gatekeeper 和 Kyverno 进行一个中立客观的比较，让用户能够据此作出决策。这里仅从 Kubernetes 的视角来对这两个项目来进行评价。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;因为本文仅仅涉及 Kubernetes，因此对后续对 OPA/Gatekeeper 项目会简称为 Gatekeeper。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;为了透明起见，我想公开说明我个人的立场。我是 Kyverno 而不是 GateKeeper 的撰稿人。我在 Kyverno 上写过几篇博客，在 Gatekeeper 上则没有。我过去还曾对 OPA Rego 提出过一些批评。然而，我的目标是把所有这些和任何个人感情放在一边，并试图以全新的方式来对待这两个项目，没有任何偏见和偏爱。&lt;/p&gt;

&lt;p&gt;在和 Kyverno 和 OPA 两个社区进行平等地沟通，让双方的管理者和贡献者公平地对比较标准和结果进行评论。在参与比较、评论等方面均没有偏向任何项目。&lt;/p&gt;

&lt;h2 id=&#34;导言&#34;&gt;导言&lt;/h2&gt;

&lt;h3 id=&#34;kubernetes-策略是什么&#34;&gt;Kubernetes 策略是什么&lt;/h3&gt;

&lt;p&gt;Kubernetes 的 Pod Security Policy，正如其名字所暗示的，仅是针对 Pod 工作的，是一种用来验证和控制 Pod 及其属性的机制。另外 PSP 只能屏蔽非法 Pod 的创建，无法执行任何补救/纠正措施。而 Gatekeeper 和 Kyverno 的作用范围就不是局限在 Pod 上，并且也有更多更深入的功能，而不只是简单的验证功能。策略引擎是一种能对整个 Kubernetes 环境进行全局控制的方法。&lt;/p&gt;

&lt;h3 id=&#34;gatekeeper-简介&#34;&gt;Gatekeeper 简介&lt;/h3&gt;

&lt;p&gt;Gatekeeper 是一个由 Google、微软等多个公司合作推出的开源项目，后来捐赠给了 CNCF。现已经历了三次迭代。Gatekeeper 是通用策略引擎 Open Policy Agent（OPA）的 Kubernetes 专用实现。由于 Open Policy Agent 与 Gatekeeper 之间的关系，该项目经常被写成“OPA Gatekeeper”来表明这层关系。Gatekeeper 实现了请求验证功能，最近还加入了变异能力。OPA 的一个主要特征是依赖于使用一种叫做 Rego 的专用编程语言，这种语言被用来实现策略决策的必要逻辑。通过 Rego，OPA 能够广泛适用于包括 Kubernetes 在内的多种不同的软件，实现高层次的逻辑操作。&lt;/p&gt;

&lt;h3 id=&#34;kyverno-简介&#34;&gt;Kyverno 简介&lt;/h3&gt;

&lt;p&gt;Kyverno 是来自 Nirmata 的开源项目，后来也捐赠给了 CNCF。和 Gatekeeper一样，Kyverno 也是一个具有验证和变异能力的 Kubernetes 策略引擎，但是它还有生成资源的功能，最近还加入了 API 对象查询的能力。与 Gatekeeper 不同，Kyverno 原本就是为 Kubernetes 编写的。和 Gatekeeper 相比，Kyverno 除了对象生成功能之外，还无需专用语言即可编写策略，从实现语言的角度上来看，Kyverno 的模型更为简洁。&lt;/p&gt;

&lt;h2 id=&#34;对比&#34;&gt;对比&lt;/h2&gt;

&lt;p&gt;下面的三个表格对两个项目的特征和质量进行分类，并试图以最客观的方式进行对比。这些维度分别是：&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;/ul&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特征/功能&lt;/th&gt;
&lt;th&gt;Gatekeeper&lt;/th&gt;
&lt;th&gt;Kyverno&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;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;变异&lt;/td&gt;
&lt;td&gt;✅（Alpha）&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;生成&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;原生策略对象&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;监控指标&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;OpenAPI 验证（&lt;code&gt;kubectl explain&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;高可用&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;API 对象查询&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅（Alpha）&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;具备测试能力的 CLI 工具&lt;/td&gt;
&lt;td&gt;✅ 独立的客户端&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;策略审计&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;社区/生态系统&lt;/th&gt;
&lt;th&gt;Gatekeeper&lt;/th&gt;
&lt;th&gt;Kyverno&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CNCF 状态&lt;/td&gt;
&lt;td&gt;毕业（OPA）&lt;/td&gt;
&lt;td&gt;沙箱&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;合作伙伴生态系统采用（注 1）&lt;/td&gt;
&lt;td&gt;◗&lt;/td&gt;
&lt;td&gt;◔&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Github 状态（星，分叉、版本、提交）&lt;/td&gt;
&lt;td&gt;1,543, 280, 38, 510&lt;/td&gt;
&lt;td&gt;702, 72, 60, 3,034&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;社区认同（注 1）&lt;/td&gt;
&lt;td&gt;◗&lt;/td&gt;
&lt;td&gt;◔&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;策略样本库&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;p&gt;注 1：无精确定义，Gatekeeper 看起来比 Kyverno 采用数量更多，但是并没有具体数字。
注 2：无客观标准，Gatekeeper 历史更长，社区认可度可能更高。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;杂项&lt;/th&gt;
&lt;th&gt;Gatekeeper&lt;/th&gt;
&lt;th&gt;Kyverno&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;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;可以在 Kubernetes 之外工作&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;诞生时间&lt;/td&gt;
&lt;td&gt;2017 年 7 月&lt;/td&gt;
&lt;td&gt;2019 年 5 月&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;创始公司&lt;/td&gt;
&lt;td&gt;Styra（OPA）&lt;/td&gt;
&lt;td&gt;Nirmata&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;文档成熟度&lt;/td&gt;
&lt;td&gt;◗（注 1）&lt;/td&gt;
&lt;td&gt;◕&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;p&gt;注 1：并没有统一的评判标准。这里的评价基于 Gatekeeper 的功能，而不是 Rego。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;分析&#34;&gt;分析&lt;/h2&gt;

&lt;p&gt;根据前面的功能对比，我做了一个简单的归纳，列出两个产品的优劣，这里只写出了标题内容，并不够详尽。&lt;/p&gt;

&lt;h3 id=&#34;gatekeeper-的优势&#34;&gt;Gatekeeper 的优势&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;能够表达非常复杂的策略；&lt;/li&gt;
&lt;li&gt;社区更为成熟；&lt;/li&gt;
&lt;li&gt;支持多副本模式，更好的可用性和伸缩性。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;gatekeeper-的劣势&#34;&gt;Gatekeeper 的劣势&lt;/h3&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;/ul&gt;

&lt;h3 id=&#34;kyverno-的优势&#34;&gt;Kyverno 的优势&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 风格的策略表达方式，非常易于编写；&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;h3 id=&#34;kyverno-的劣势&#34;&gt;Kyverno 的劣势&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;受到语言能力的限制，难以实现复杂策略；&lt;/li&gt;
&lt;li&gt;较为年轻，社区接受度不高；&lt;/li&gt;
&lt;li&gt;API 对象查询能力还很初级；&lt;/li&gt;
&lt;li&gt;没有高可用能力（还在路线图阶段）。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;警告：下面的内容是我根据前面的对比表和优势劣势列表，再加上自己对这两个工具的体验，以及在云原生社区的走访，综合起来的意见分析。如果你没有兴趣看我的观点，文章就到此为止了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes 是一个声明式的系统：用户向 Kubernetes 提出对状态的要求，Kubernetes 通过各种控制器，去协调观察到的状态，以使其与用户期望的状态一致。这就是云原生平台的核心价值主张。为了实现这一目标，逻辑实现的重任从用户身上转移到了平台本身。每个资源类型都存在一些内部逻辑，这些逻辑就是协调其状态所需的能力。对于 Gatekeeper 来说，到目前为止最大的弱点是它需要一种叫做 Rego 的专门的编程语言来实现这种逻辑，这种语言在其他地方都无法使用。这是一个现实，因为 OPA 是一个通用的策略引擎。只有通过 Gatekeeper 将其改编成 Kubernetes 形式，才能利用其能力。那么实际上，用户负责描述他们希望调和的对象（策略），以及提供必要的逻辑（Rego）来调和它。使用外部 DSL 来管理 Kubernetes 策略，在很多方面都会变得繁琐和复杂，并给项目增加技术债务。作为一种权衡，其明显的优势是可以实现非常强大的策略。毕竟，当一个人需要编写一种编程语言时，他只受限于该语言的能力及其输入。不过，如果可以在其他地方利用 OPA，就可以分摊这种费用。&lt;/p&gt;

&lt;p&gt;相比 Gatekeeper 来说，Kyverno 的第一印象就是没有那么复杂的技术需求。因为它是专门为 Kubernetes 构建的，并且用声明式的方法来表达策略，所以它的心理模型与 Kubernetes 对象的描述和协调方式是相同的。执行策略决策所需的逻辑被从用户的负担中移除，成为工具本身的领域。这种模式导致策略的编写方式得到了极大的简化，全面的降低了策略引擎的使用难度。Kyverno 的编译和生成能力，使它从一个简单的准入控制器转变为一个真正的自动化工具。通过结合这三种能力，再加上最近增加的 API 查询能力，Kyverno 能够执行 Gatekeeper 所不能执行的任务，而且还能够消除可能在整个集群和/或组织中分散使用的其他和不同的工具。这种简单性加上它的自动化能力和对其他工具的整合，为新用户以及有经验的用户和操作者带来了巨大的价值。&lt;/p&gt;

&lt;p&gt;根据所介绍的信息，我认为 Kyverno 应该是应用 Kubernetes 策略的一个比较自然的选择。但如果用户符合下面两个用例中的一种或两种，就更应该选择 Gatekeeper。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;有一种需求和具体意图，使用一致的核心工具将策略应用于组织内不同的系统（即，不仅仅是Kubernetes）。&lt;/p&gt;

&lt;p&gt;反对意见：根据我的经验，无论是在云原生社区内部还是外部，大多数组织目前已经在使用其他工具将策略应用于现有系统。这通常是因为这些系统以及为这些系统实施策略的软件在 Kubernetes 以及 OPA 和 Gatekeeper 之前就已经存在。此外，这些现有工具通常不要求使用编程语言来实现其策略。因此，考虑到现有的知识、运营和资本投资，大多数组织不太可能为了实现工具一致性带来的价值，选择放弃这些工具，转而使用技术负担较重的新工具。&lt;/p&gt;

&lt;p&gt;太长不看：如果你正在寻找一个跨 Kubernetes 和其他系统使用的单一策略引擎，Kyverno 不适合你。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;策略的复杂度很高。&lt;/p&gt;

&lt;p&gt;反对意见：根据我的经验，大多数 Kubernetes 用户都没有使用包括 PSP 在内的任何策略支持。而 2020 年对在 AWS 上运行容器化工作负载的客户的调查也得到了类似的结果，只有 49% 的客户使用策略。这些用户中的绝大多数都在做的是重复的策略——例如“容器不应该有特权”或“确保所有命名空间都带有给定的标签”或“验证 Pods 没有使用 hostPath 卷”等。“复杂”这个词是相对的，有点主观，但这样的策略表达方式绝对不复杂。Kyverno 允许以最简单的形式编写策略，这反过来又更容易推理和维护。如果要为一个更复杂、更困难的工具支付额外的价格，就应该尽量物尽其用，否则无法获得价值。&lt;/p&gt;

&lt;p&gt;太长不看：如果无需实现高度复杂的策略，Gatekeeper 不会带来好处。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Gatekeeper 和 Kyverno 项目本身都是有价值、有能力的策略引擎，每个项目都有各自的优缺点。最终，用户应该根据自己的需求和限制条件进行评估并做出最明智的决定，但作为一般建议，所有生产用户都应该计划使用策略引擎来保护集群的安全并简化 Kubernetes 管理。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：网络策略可视化编辑器</title>
      <link>/post/introducing-network-policy-editor/</link>
      <pubDate>Fri, 12 Feb 2021 09:05:21 +0800</pubDate>
      <guid>/post/introducing-network-policy-editor/</guid>
      <description>

&lt;h2 id=&#34;引子&#34;&gt;引子&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;跳过本节不影响阅读&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;既然是牛年第一篇，总要写点废话起个头。另外写小工具系列经常面对的一个难题就是——怎样凑够 300 字的原创门槛。&lt;/p&gt;

&lt;p&gt;2020 年有大半年我都在唠叨云原生安全的事情，现在的情况按照我的理解，有点像 2000 年附近的互联网——各种东西都在往新瓶子里装，那时候的新瓶子是互联网，现在的新瓶子是云原生；那时候的旧酒是邮件、留言板、传呼机，现在的旧酒除了这些生活内容之外，多了更多的 ToB/G 的内容；从前的较大规模的公开互联网服务多数是自建自维护的，现在的公开服务则往往会用到大量的公有云、SaaS/PaaS 服务以及第三方交付项目。&lt;/p&gt;

&lt;p&gt;在 Kubernetes 来说：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;推出了 CKS 认证&lt;/li&gt;
&lt;li&gt;OPA 毕业&lt;/li&gt;
&lt;li&gt;Kyverno 进入沙箱&lt;/li&gt;
&lt;li&gt;Redhat 收购 StackRox&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;综上，经过轰轰烈烈的云原生运动之后，安全问题就已经被怼到了我们面前，很多时候一些重点服务一旦遭到破坏，虽说安全相关的黑产已经有了成熟的获利链条；然而在责任方来说，的确可以挤出一句——这不是钱的事儿。&lt;/p&gt;

&lt;h2 id=&#34;正文&#34;&gt;正文&lt;/h2&gt;

&lt;p&gt;言归正传，今天要介绍的是一个&lt;strong&gt;安全相关&lt;/strong&gt;的网络策略&lt;strong&gt;小工具&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;网络策略是 Kubernetes 内置的重要安全机制之一，用它可以轻松地使用 Namespace、Label Selector、CIDR 等方式，限制 Pod 的 Ingress/Egress 访问，相对于 RBAC 来说，这东西其实更贴近传统网络策略的限制方式，但是目前应用还较少，也就显得比较难用了。Cilium 推出的这个可视化编辑器，是个很好的入门工具。&lt;/p&gt;

&lt;p&gt;缺省界面如图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/policy-editor-ui.png&#34; alt=&#34;default&#34; /&gt;&lt;/p&gt;

&lt;p&gt;整个界面分为几个部分：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A：可视化编辑区：区域中可以用点选的方式产生不同的 Ingress Egress 规则。&lt;/li&gt;
&lt;li&gt;B：代码编辑区：交互式编辑区产生的策略变更都会用 YAML 代码的形式反映在该区域，另外左上角还可以通过上传的方式载入现有 YAML 文件，上传的内容也会反向映射回到可视化编辑区中。&lt;/li&gt;
&lt;li&gt;C：该区域是教学区域，下拉菜单中包含了几个常用的策略的相关教学，注意这里的是教学，给出的可能是一个待修复的策略和修复方法，不要直接复制使用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可视化编辑区分为左中右三个部分：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1：这里可以定义策略的主体，例如命名空间、名称、策略对应的管理目标（Pod Selector），另外还可以定义 Ingress 和 Egress 的缺省行为，例如缺省 Deny 或者 Allow，这里可以看到，随着策略的变化，相应的连接线颜色会发生变化。&lt;/li&gt;
&lt;li&gt;2：Ingress 区域，管理进入 Pod 的流量，分为集群外、集群内、本命名空间三种配置方法。&lt;/li&gt;
&lt;li&gt;3：Egress 区域，管理从 Pod 发出的流量，和 Ingress 区域一样，分为三种配置。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如新建一个缺省禁止所有其它命名空间访问的策略，只要新建策略，在 1 区点击 &lt;code&gt;Ingress&lt;/code&gt;，将其设为 Default Deny，然后在 2 区点击 &lt;code&gt;In Namespace&lt;/code&gt;，在弹出窗口中选择 &lt;code&gt;Allow from any pod&lt;/code&gt; 即可。最终结果如图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/only-ns-ingress.png&#34; alt=&#34;only-ns&#34; /&gt;&lt;/p&gt;

&lt;p&gt;除了 All 之外，编辑器还支持 namespaceSelector、podSelector 以及集群外 CIDR 等的源头选择。Egress 策略中也包括对目标端口的设置。&lt;/p&gt;

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

&lt;p&gt;这才能叫可视化吧。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;工具地址：&lt;code&gt;https://editor.cilium.io/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Kubernetes Network Policies：&lt;code&gt;https://kubernetes.io/docs/concepts/services-networking/network-policies/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>写给小白的 kubectl 入门</title>
      <link>/post/kubectl-in-5-min/</link>
      <pubDate>Tue, 02 Feb 2021 22:19:49 +0800</pubDate>
      <guid>/post/kubectl-in-5-min/</guid>
      <description>

&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;

&lt;p&gt;意外又看到不少正在学 Kubernetes 新手。想想本人写过各种自己懂或不懂、信或不信的原理、机制、方法和工具等等各种东西，唯独没写过 kubectl，其实这东西也是值得一写的——比如说去年我才从一线同学的操作里学会用 &lt;code&gt;-A&lt;/code&gt; 代替 &lt;code&gt;--all-namespaces&lt;/code&gt;。理顺 kubectl 的用法，也会对 Kubernetes 的知识体系以及运维工作有很大的帮助。&lt;/p&gt;

&lt;p&gt;对 Kubernetes 稍有了解的读者应该都知道声明式 API 的说法，kubectl 就是一个这种 API 的客户端，所以 kubectl 的主要功能就是用来操作对象的。&lt;/p&gt;

&lt;h2 id=&#34;开局两张图&#34;&gt;开局两张图&lt;/h2&gt;

&lt;p&gt;下图是个常见的使用方式：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/kubectl-command.png&#34; alt=&#34;主谓宾&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;其实本来想写主谓宾定状补的，后来想想还得复习一下，算了算了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;一般的 kubectl 使用都是这么个顺序，参数是可以调整位置的，暂且如此就可以了。&lt;/p&gt;

&lt;p&gt;用一个思维导图来归纳一下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/kubectl-mindmap.png&#34; alt=&#34;mindmap&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;动作&#34;&gt;动作&lt;/h2&gt;

&lt;p&gt;在 kubectl 中被称为 &lt;code&gt;command&lt;/code&gt; 也就是命令。使用 &lt;code&gt;kubectl --help&lt;/code&gt; 能看到可用的命令列表：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl --help
kubectl controls the Kubernetes cluster manager.

 Find more information at: https://kubernetes.io/docs/reference/kubectl/overview/

Basic Commands (Beginner):
  create        Create a resource from a file or from stdin.
...
  run           在集群中运行一个指定的镜像
...

Basic Commands (Intermediate):
  explain       查看资源的文档
  get           显示一个或更多 resources
...
Deploy Commands:
  rollout       Manage the rollout of a resource
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到 kubectl 的命令行帮助非常不错，不仅有功能说明、分类，还有难度标识，甚至有部分的中文说明，kubectl 的每个命令都可以用 &lt;code&gt;--help&lt;/code&gt; 查看进一步的帮助说明。&lt;/p&gt;

&lt;p&gt;这里列出了很多可用的命令，按照操作能力，主流命令基本可以分为增删改查（CRUD）四种。&lt;/p&gt;

&lt;h3 id=&#34;c&#34;&gt;C&lt;/h3&gt;

&lt;p&gt;新建命令用于在集群中创建对象，最常用的新建命令应该是 &lt;code&gt;create&lt;/code&gt;、&lt;code&gt;run&lt;/code&gt; 了，&lt;code&gt;create&lt;/code&gt; 能够创建多种对象，而 &lt;code&gt;run&lt;/code&gt; 则主要用来创建 Pod。这两个命令都需要在命令行中使用参数的方式来表达待创建的对象的字段内容，其表达力非常粗糙和有限，并且带有明显的命令式 API 风味，在我的日常工作中已经很少用到这样的命令了。&lt;/p&gt;

&lt;p&gt;但是这种命令往往有个妙用，&lt;code&gt;--dry-run=client&lt;/code&gt;（旧版本中是 &lt;code&gt;--dry-run&lt;/code&gt;），可以在不产生实际操作的情况下，测试命令的输出，加上 &lt;code&gt;-o yaml&lt;/code&gt;，可以帮助输出 YAML 文档。&lt;/p&gt;

&lt;h3 id=&#34;r&#34;&gt;R&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;get&lt;/code&gt; 是最常用的查询指令，用于获取对象列表和基本信息，而 &lt;code&gt;describe&lt;/code&gt; 则用于获取一个对象的详细信息。另外一个常用的读取指令就是 Debug 常用的日志查看指令：&lt;code&gt;kubectl logs&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&#34;u&#34;&gt;U&lt;/h3&gt;

&lt;p&gt;最重要的更新命令可以说是 &lt;code&gt;apply&lt;/code&gt;，&lt;code&gt;edit&lt;/code&gt; 了，&lt;code&gt;patch&lt;/code&gt;、&lt;code&gt;label&lt;/code&gt;、&lt;code&gt;annotation&lt;/code&gt;、&lt;code&gt;scale&lt;/code&gt; 等命令也算常用。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;apply&lt;/code&gt; 是把 yaml 提交给 Kubernetes 集群的最常用方式，而 &lt;code&gt;edit&lt;/code&gt; &lt;code&gt;patch&lt;/code&gt; 都是用于修改线上负载的常用手段。&lt;code&gt;label&lt;/code&gt; 和 &lt;code&gt;annotation&lt;/code&gt; 命令则是用于修改对象元数据的，例如标签和注解。&lt;/p&gt;

&lt;h3 id=&#34;d&#34;&gt;D&lt;/h3&gt;

&lt;p&gt;这个没什么好说——&lt;code&gt;delete&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&#34;获取帮助&#34;&gt;获取帮助&lt;/h3&gt;

&lt;p&gt;kubectl 的所有命令、子命令都支持 &lt;code&gt;--help&lt;/code&gt; 参数，可以用这种方式获取帮助。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl options&lt;/code&gt; 命令能够获取 kubectl 的所有全局参数。&lt;/p&gt;

&lt;h3 id=&#34;常用参数&#34;&gt;常用参数&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;-f&lt;/code&gt;：很多指令（不只是 &lt;code&gt;apply&lt;/code&gt; 和 &lt;code&gt;create&lt;/code&gt;）都可以用 &lt;code&gt;-f &amp;lt;文件名&amp;gt;&lt;/code&gt; 的方式进行输入，如果使用管道操作，则可以用参数 &lt;code&gt;-f -&lt;/code&gt; 接收 STDIN 的输入。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-l&lt;/code&gt;：可以使用各种对象上的标签对操作范围进行过滤，例如 &lt;code&gt;-l app=hello&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-o&lt;/code&gt;：指定输出格式，这个参数相对复杂，最常用的是 &lt;code&gt;yaml&lt;/code&gt; 或者 &lt;code&gt;json&lt;/code&gt; 用于输出机器报文，还可以用 JSON Path 或者 Go Template 对结果进行处理。&lt;/p&gt;

&lt;h2 id=&#34;对象&#34;&gt;对象&lt;/h2&gt;

&lt;p&gt;对象通常是类型+名称的一个组合，可以用 kubectl 获得当前集群支持的对象类型：&lt;/p&gt;

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

&lt;p&gt;如上图，输出内容包含几个列：名称、简称、API 群组、是否归属命名空间以及对象的 Kind 属性。例如常用的 &lt;code&gt;Deployment&lt;/code&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;名称：Deployment&lt;/li&gt;
&lt;li&gt;简称：Deploy&lt;/li&gt;
&lt;li&gt;API 群组：apps&lt;/li&gt;
&lt;li&gt;归属命名空间：是&lt;/li&gt;
&lt;li&gt;Kind：Deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用命令 &lt;code&gt;kubectl get deploy&lt;/code&gt;，就能获得当前命名空间中的 &lt;code&gt;Deployment&lt;/code&gt; 对象列表，如果在尾巴上加入 Deployment 的名称，就能得到符合名称要求的 Deployment 对象，&lt;/p&gt;

&lt;h2 id=&#34;schema&#34;&gt;Schema&lt;/h2&gt;

&lt;p&gt;前面提到的 &lt;code&gt;-f&lt;/code&gt; 参数，或者是 &lt;code&gt;get -o yaml&lt;/code&gt;，都要用到具体的对象数据结构，这个结构到底是哪里规定的呢？基本结构可以分为三个部分，以一个 Namespace 为例：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Namespace
metadata:
  name: default
spec:
  finalizers:
  - kubernetes
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;一般会分为四个基础字段：&lt;code&gt;apiVersion&lt;/code&gt;、&lt;code&gt;kind&lt;/code&gt;、&lt;code&gt;metadata&lt;/code&gt;、&lt;code&gt;status&lt;/code&gt; 以及 &lt;code&gt;spec&lt;/code&gt;。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apiVersion：格式为 &lt;code&gt;&amp;lt;apiGroup&amp;gt;/&amp;lt;apiVersion&amp;gt;&lt;/code&gt;，一个对象的 API Group，可以用前文提到的 &lt;code&gt;api-resources&lt;/code&gt; 命令查到，而版本则可以通过 &lt;code&gt;kubectl api-versions&lt;/code&gt; 查询得到。&lt;/li&gt;
&lt;li&gt;kind：对应 &lt;code&gt;api-resources&lt;/code&gt; 命令输出的字段。&lt;/li&gt;
&lt;li&gt;metadata：元数据，其中包括标签、注解、名称等字段，如果对象是属于命名空间的，也会把命名空间写在这里。&lt;/li&gt;
&lt;li&gt;status：这个字段的内容通常是由 Kubenretes 自动填写的。经常会被省略掉。&lt;/li&gt;
&lt;li&gt;spec：具体的对象内容，可以由几个途径获取其定义结构

&lt;ul&gt;
&lt;li&gt;部分资源可以使用 &lt;code&gt;kubectl explain &amp;lt;对象类别&amp;gt;&lt;/code&gt; 获得解释&lt;/li&gt;
&lt;li&gt;如果该资源在集群中有对象存在，可以使用 &lt;code&gt;kubectl get &amp;lt;对象类别&amp;gt; &amp;lt;对象名称&amp;gt; -o yaml&lt;/code&gt; 的方式获得原文，向其致敬。&lt;/li&gt;
&lt;li&gt;如果前两种方法都没有，就需要去查看 Kubernetes 或者第三方的 API Reference 了。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;最后&#34;&gt;最后&lt;/h2&gt;

&lt;p&gt;看了上面的解释，是不是对 Kubernetes 的控制台操作有点底了？&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>我就要在容器里写文件！</title>
      <link>/post/how-to-writefiles-inside-k8s-container/</link>
      <pubDate>Sun, 31 Jan 2021 00:43:14 +0800</pubDate>
      <guid>/post/how-to-writefiles-inside-k8s-container/</guid>
      <description>

&lt;p&gt;在生产环境上遇到过一个讨厌的事情，有业务应用在容器中写入大量日志，导致磁盘空间爆满，引发了大面积驱逐。这种情况还有个比较烦心的事情就是无从监控，因此也不能用告警解决。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;docker ps --size&lt;/code&gt; 可以凑合试一下。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;虽说提倡使用标准输出来打印日志，通过 Docker 参数控制日志翻转，但是第三方容器的写入很难劝说整改，并且客观存在写入临时文件等的情况，所以还是要想个办法，在受控的情况下进行写入。&lt;/p&gt;

&lt;p&gt;为了防止这种情况的出现，很自然的想出对策——避免容器内的任意写入行为，在 &lt;code&gt;securityContext&lt;/code&gt; 里面加上 &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; 就可以了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  containers:
  - image: dustise/sleep:v0.9.6
    name: sleep
    securityContext:
      readOnlyRootFilesystem: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个容器创建之后，进入容器发现已经无法写入了。要写入文件，必须使用加载卷的方式，才能在指定位置进行写入，限制了卷尺寸，也就限制了文件写入了。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  containers:
  - image: dustise/sleep:v0.9.6
    name: sleep
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir:
      sizeLimit: &amp;quot;100Mi&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里我们创建了一个限制尺寸为 100Mi 的卷，然后进入 Pod 尝试写入一个 500M 的文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ df -h /cache
Filesystem                Size      Used Available Use% Mounted on
/dev/vda1                98.3G     17.3G     76.8G  18% /cache

$ dd if=/dev/zero of=/cache/data.txt bs=500M count=1
1+0 records in
1+0 records out
$ command terminated with exit code 137
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到这个卷的尺寸并非我们指定的 100M，而是整个数据盘的大小，后面文件成功写入了，但是过几分钟之后，Shell 被踢出，发现 Pod 被驱逐了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
sleep-68bc88849b-5rwrd   0/1     Evicted   0          7m48s
sleep-68bc88849b-b7wz5   1/1     Running   0          104s

$ kubectl describe po sleep-68bc88849b-5rwrd
...
  Warning  Evicted    2m42s  kubelet            Usage of EmptyDir volume &amp;quot;cache-volume&amp;quot; exceeds the limit &amp;quot;100Mi&amp;quot;.
  Normal   Killing    2m42s  kubelet            Stopping container sleep
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到驱逐原因的确是超出了限制。&lt;/p&gt;

&lt;p&gt;驱逐是个比较慢的过程，可能不太合适，我们知道，emptyDir 还有一种内存的方式，会有改善么？&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;emptyDir:
  sizeLimit: &amp;quot;100Mi&amp;quot;
  medium: &amp;quot;Memory&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;同样测试，发现还是被驱逐了。这种类型的卷是会占用容器的内存限制的（这点在实际应用时一定要注意，防止 emptyDir 挤占业务应用内存），加上 limit 会不会有所好转呢？&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;resources:
  requests:
    memory: &amp;quot;300Mi&amp;quot;
  limits:
    memory: &amp;quot;300Mi&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次进入容器：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ df -h /cache
Filesystem                Size      Used Available Use% Mounted on
tmpfs                     7.7G    100.0M      7.6G   1% /cache

$ dd if=/dev/zero of=/cache/data.txt bs=500M count=1
dd: error writing &#39;/cache/data.txt&#39;: Out of memory
0+1 records in
0+0 records out
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这次的反应速度就很快了，直接被拒绝。&lt;/p&gt;

&lt;p&gt;Limit 这么好用，如果直接限制 &lt;code&gt;Ephemeral Storage&lt;/code&gt; 会不会又能快速反应，又不影响内存占用呢？答案是否定的，这个资源的 Limit 同样是使用驱逐机制实现的。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1.20 中新增了一个 Feature gate：SizeMemoryBackedVolumes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如果放弃内存 emptyDir 的话，hostPath 能满足要求么？&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: PersistentVolume
metadata:
  name: task-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: &amp;quot;/mnt/data&amp;quot;
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: task-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;申请了一个 1G 的 PVC 之后，再尝试创建文件，很遗憾，最终我在 &lt;code&gt;/mnt/data&lt;/code&gt; 目录下收获了 10G 的文件。甚至也没有驱逐过程，换用 Local Persistent Volume 也没有任何变化。&lt;/p&gt;

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

&lt;p&gt;Kubernetes 容器中写入文件是一个非常不推荐的操作。两种本地卷都无法达成限制目的。如果没有外部存储设施，仅能通过 emptyDir 实现简单的写入限制，内存类型的会侵占应用内存，而磁盘类型的限制则要依赖于驱逐机制相对迟缓地完成操作。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：Dyff</title>
      <link>/post/kubernetes-toolbox-dyff/</link>
      <pubDate>Sun, 24 Jan 2021 16:07:19 +0800</pubDate>
      <guid>/post/kubernetes-toolbox-dyff/</guid>
      <description>

&lt;p&gt;YAML 工人经常需要对类似的 YAML 进行对比，查看其中的更新或者状态变化，通用的 diff 工具经常是能胜任的，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ diff n1.yaml n2.yaml
...
&amp;lt;       - image: dustise/sleep:v0.9.5
---
&amp;gt;       - image: dustise/sleep:v0.9.6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;但是如果出现了字段的平移，或者新加了一些元素，结果可能就比较难看了，例如移动几个字段的位置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ diff n1.yaml n2.yaml
11,13d11
&amp;lt;   progressDeadlineSeconds: 600
&amp;lt;   replicas: 1
&amp;lt;   revisionHistoryLimit: 10
19d16
&amp;lt;       maxSurge: 25%
20a18
&amp;gt;       maxSurge: 25%
21a20,22
&amp;gt;   progressDeadlineSeconds: 500
&amp;gt;   replicas: 1
&amp;gt;   revisionHistoryLimit: 10
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这几个字段发生变化并移位之后，就比较难用肉眼观测具体的有效变更了。这种情况下，Dyff 就可以派上用场了。&lt;/p&gt;

&lt;p&gt;安装很容易，在 macOS 中可以使用 Homebrew 进行安装：&lt;code&gt;brew install homeport/tap/dyff&lt;/code&gt;，Linux 和 macOS 中也可以使用脚本 &lt;code&gt;curl --silent --location https://tinyurl.com/y4qvdl4d |bash&lt;/code&gt; 进行安装。&lt;/p&gt;

&lt;p&gt;官方文档中展示了它的基本子命令 &lt;code&gt;between&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/dyff-bw-example.png&#34; alt=&#34;between&#34; /&gt;&lt;/p&gt;

&lt;p&gt;如果对一个手写的 yaml 和它的线上版本进行对比，可能会有很麻烦的输出，例如 &lt;code&gt;managedFiled&lt;/code&gt;、&lt;code&gt;status&lt;/code&gt; 等字段：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get deploy sleep -o yaml | dyff bw sleep.yaml  -
...
+ one map entry added:
  status:
...
metadata
  + six map entries added:
    creationTimestamp: 2021-01-24T07:51:40Z
    generation: 1
    managedFields:
...
spec.template.spec
  + one map entry added:
    securityContext: {}

spec.template.spec.containers.sleep
  + one map entry added:
    resources: {}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里可以用 &lt;code&gt;krew&lt;/code&gt; 目录中的 &lt;code&gt;neat&lt;/code&gt; 插件进行处理，这个插件能将线上运行对象所输出的 YAML 进行清理，能大大地减少状态字段的干扰，同样也支持管道操作，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;kubectl get deploy sleep -o yaml | kubectl neat - |  dyff bw sleep.yaml  -
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样处理一下之后，视野就会清楚很多了。&lt;/p&gt;

&lt;p&gt;除了对比之外，dyff 还支持 yaml 和 json 的互转，加上 neat 的操作可以是这样：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get deploy sleep -o yaml | kubectl neat - | dyff json -
{
  &amp;quot;apiVersion&amp;quot;: &amp;quot;apps/v1&amp;quot;,
  &amp;quot;kind&amp;quot;: &amp;quot;Deployment&amp;quot;,
  &amp;quot;metadata&amp;quot;: {
    &amp;quot;annotations&amp;quot;: {
      &amp;quot;deployment.kubernetes.io/revision&amp;quot;: &amp;quot;1&amp;quot;
    },
&lt;/code&gt;&lt;/pre&gt;

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

&lt;ul&gt;
&lt;li&gt;Dyff：&lt;code&gt;https://github.com/homeport/dyff&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Neat：&lt;code&gt;https://github.com/itaysk/kubectl-neat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Krew：&lt;code&gt;https://github.com/kubernetes-sigs/krew&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>送容器下乡</title>
      <link>/post/down2countryside/</link>
      <pubDate>Wed, 06 Jan 2021 14:50:56 +0800</pubDate>
      <guid>/post/down2countryside/</guid>
      <description>

&lt;h2 id=&#34;kubernetes-上天了&#34;&gt;Kubernetes 上天了&lt;/h2&gt;

&lt;p&gt;2020 年里，Kubernetes 的疆界有了一个有趣的扩展——美国人把 Kubernetes 和 Istio 装到了 F16 战斗机上。战斗机应该算是&lt;code&gt;真&lt;/code&gt;边缘了吧？读了几篇相关材料，发现整个过程远不止一个极限部署这么简单，DoD 在军方的大背景下，实现了一整套基于 DevSecOps 理念的云原生生态，那么一个问题就是，为什么单独要说 Kubernetes 和 Istio 呢？只是因为热门吗？&lt;/p&gt;

&lt;p&gt;我的看法是，容器化和容器编排，是云原生的“阵眼”。云原生是个覆盖方方面面的体系，除了我们熟知的容器链条等技术要素之外，还以方法论的方式渗透到整个 IT 环境的市场、商务、架构、开发、运维、安全等各个方面。而其中的容器技术，其底蕴来自于几十年来整个业界不断的虚拟化和隔离技术的积累，是云原生的众多概念中，最能被“看得见，摸得着”的形象。同时作为制品和运行时的一等公民，容器和（Kubernetes 的）声明式 API 结合起来，已经能够满足绝大多数业务应用的运行需要。一个常见的 Kubernetes 环境，有足够条件能够符合 12 要素中至少一半的要求。这个组合是最常见也是最应该的云原生入门选择。很大程度上，Kubernetes 能走到哪里，云原生才能走到哪里。&lt;/p&gt;

&lt;h2 id=&#34;部署是个大问题&#34;&gt;部署是个大问题&lt;/h2&gt;

&lt;p&gt;回到前面的新闻，把 Kubernetes 装到哪里，当然不代表成功，但是它代表了一个重要的方向，YAML 架构师们都知道——只要这东西起来了，给我一个 Helm，就能搞他个天翻地覆。所以从诞生之初直到现在，Kubernetes 的部署都是个大问题。&lt;/p&gt;

&lt;p&gt;然而一谈到 F16 之流的边缘部署，不可避免的会想到奇奇怪怪的设备们，长期以来都有一个固定的句式——我们给 XX 减肥，把它塞到资源有限的 YY 设备里。不过这对 Kubernetes 可能不太合适。&lt;/p&gt;

&lt;p&gt;我一直对“魔改”这个事情有点抵触——感觉像是在车子上跳下来，虽然会有一个更高的速度，但是很难保障你真的就是火箭鱼雷航天飞机，下车才是刚起步，更多的情况是，跳车之后快了一瞬间，才发现跟不上了。&lt;/p&gt;

&lt;p&gt;资源不足的设备，和上不了容器的用是一样的，如果存在真正的需求，它们自然会适应实际需要，无法适应只能说是需求不强。强扭的瓜不甜，只想要瓜不管甜不甜的可以忽略。&lt;/p&gt;

&lt;p&gt;所以在我一个 YAML 架构师的眼里，Kubernetes 下乡，应该是基于原装的 Kubernetes，在一定程度内，满足大部分容器化业务的支撑需要，其它的东西，应该是设备归设备、虚机归虚机。Kubernetes 目前的下乡重点，应该在边缘机房，而非末梢节点。&lt;/p&gt;

&lt;h2 id=&#34;什么样的-kubernetes-能下乡&#34;&gt;什么样的 Kubernetes 能下乡&lt;/h2&gt;

&lt;p&gt;那么要让 Kubernetes 下乡，除了要求“原装货”，乡下有点什么不一样呢？&lt;/p&gt;

&lt;h3 id=&#34;非标准环境&#34;&gt;非标准环境&lt;/h3&gt;

&lt;p&gt;通常的边缘环境不会是标准数据中心，少到两三台利旧服务器，大到几个一体化机柜，各个节点会有参差不齐的硬件水平和规模，散热、供电水平通常达不到一个持续高可用运行的需要。&lt;/p&gt;

&lt;h3 id=&#34;弱网络&#34;&gt;弱网络&lt;/h3&gt;

&lt;p&gt;和散热供电一样，位于边缘的节点的网络可能会有较高的延迟，甚至较长时间的断网，周期性的网络不可用，以及需要隧道才能互访的情况。&lt;/p&gt;

&lt;p&gt;此外还有跨地域边缘节点组成的集群，节点之间、节点和控制平面之间的通信同时都可能遭遇网络问题，会把情况进一步的复杂化。&lt;/p&gt;

&lt;h3 id=&#34;反锁定&#34;&gt;反锁定&lt;/h3&gt;

&lt;p&gt;我们历尽千辛万苦将 Kubernetes 送到乡下之后，可能会有很多嗷嗷待哺的容器化应用要运行，以及各方厂商的种种设备尝试接入进行就近处理，因此对通用性的需要是显而易见的，简单说就是远端的计算节点应该有足够的软硬件兼容性，能够以一定的标准运行在通用硬件、虚拟化和操作系统上，支撑多种厂商的、或通用或边缘的软件系统的运行。&lt;/p&gt;

&lt;h3 id=&#34;低运维&#34;&gt;低运维&lt;/h3&gt;

&lt;p&gt;通常来说，运维人员还是围绕数据中心工作的，被“下放”的 Kubernetes 必须能够在一个少运维甚至零运维的情况下运行，原本在数据中心如臂使指的虚拟化、Ansible 之流可能都会因为前方条件的不足而受到种种限制，此时就要求我们的远端节点有强大的自愈、自治和被远程运维的能力。&lt;/p&gt;

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

&lt;p&gt;这几天偶尔看了一些边缘集群的一些东西，看到减肥蔚然成风，想起多年以前我对 Java 太吃内存的嘲讽，有感而发。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>（闲聊）听说 K8s 要甩了 Docker？</title>
      <link>/post/gossip-k8s-docker/</link>
      <pubDate>Wed, 02 Dec 2020 21:01:13 +0800</pubDate>
      <guid>/post/gossip-k8s-docker/</guid>
      <description>&lt;p&gt;今天偶然看到 Kubernetes 1.20 的 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md&#34; target=&#34;_blank&#34;&gt;ChangeLog&lt;/a&gt;，其中有一行大动作：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;Deprecation
Docker support in the kubelet is now deprecated and will be removed in a future release. The kubelet uses a module called &amp;quot;dockershim&amp;quot; which implements CRI support for Docker and it has seen maintenance issues in the Kubernetes community. We encourage you to evaluate moving to a container runtime that is a full-fledged implementation of CRI (v1alpha1 or v1 compliant) as they become available. (#94624, @dims) [SIG Node]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;大意是，Kubelet 中的 Docker 支持已经进入淘汰阶段，将在未来移除。原因是 Kubelet 中使用 &lt;code&gt;dockershim&lt;/code&gt; 组件为 Docker 提供了 CRI 支持，Kubernetes 认为维护这个组件是有问题的。建议用户评估并迁移到 CRI 支持更完善的运行时上。&lt;/p&gt;

&lt;p&gt;其中引用了 9 月提出的 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/94624&#34; target=&#34;_blank&#34;&gt;PR #94624&lt;/a&gt;。其中提出，为了使用 Docker，从 moby 进行了大量移植开发了 dockershim 嵌入到 Kubelet 之中。Kubelet 和 CRI 的正确沟通方式是像 containerd、cri-o 这样。各自使用独自的进程，互相以 gRPC 进行对接。Docker 目前仍然是主流，进行迁移需要广而告之并逐步推进。&lt;/p&gt;

&lt;p&gt;实际上早在 2018 年 5 月，Kubernetes 的 Containerd 集成就已经宣告了 GA。其中有两张图很能说明问题：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/containerd.png&#34; alt=&#34;images/containerd.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在 1.0 中，Kubelet 使用 Docker Shim 和 Docker 进行通信，Docker 再和下面的 containerd 进行通信。&lt;/p&gt;

&lt;p&gt;此时如果采用 containerd 作为运行时，Kubelet 要使用 CRI Containerd 和 Containerd 打交道，不过相对于 Docker，还是少了一跳。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/cri-containerd.png&#34; alt=&#34;images/cri-containerd.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在 1.1 中这个结构得到了优化——Containerd 直接内置 CRI 接口，Kubelet 甩掉包袱可以直接用 CRI 方式对 Containerd 进行控制，这样就又省了一跳。&lt;/p&gt;

&lt;p&gt;此时 Docker 在这个调用链上的位置已经有点尴尬。随着其它 CRI 运行时的发展，这种尴尬越发明显。&lt;code&gt;#94624&lt;/code&gt; 中提到过，Docker 有个优势就是提供了 Build 等“Kubelet 不需要但是很有用”的功能；然而换个角度来看，这些功能是有悖于单一职责的原则的。&lt;/p&gt;

&lt;p&gt;个人认为，Docker 这样的全能选手，在计算节点上的长期存在证明了这个阶段里，计算节点还没有进入理想的 &lt;code&gt;cattle&lt;/code&gt; 状态，用户一方面还没有心思对“多余”的功能进行剪裁，另一方面还有可能人工进入节点上进行运行时范围以外的操作。在 GA 一年多之后，砍刀开始落下，说明了什么呢？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;容器和 Docker 这两个经常被混用的词，其间的边界可能会变得越来越清晰，构建、运行、管理越来越倾向于使用各自领域的专业工具各司其职；&lt;/li&gt;
&lt;li&gt;计算节点会变得更加“没性格”，换句话说，仅为了“运行容器”为目的的基础设施软件，例如操作系统、CRI 这样的工具会逐步代替大而全的通用 Linux Server 操作系统和 Docker 出现在容器节点上；&lt;/li&gt;
&lt;li&gt;“没性格”的计算节点将会更加容易地被创建、运行、调整和销毁，也就是说会提高容器集群规模的伸缩能力，甚至逐渐形成普遍的动态扩缩容能力。&lt;/li&gt;
&lt;li&gt;集群级别的批量化、自动化运维能力的要求会越来越高——或者以后的节点上没有 ssh、vim 也未可知。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;带点个人感情的说，前两天刚刚遭遇 DockerHub 限流的我还是生出了一点卑鄙的快意，Google 的铁拳再一次敲在了 Docker 的头上，Docker EE 怎么办？但是 Docker Desktop for Mac 还是真香的。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个不太小的工具：Shell Operator</title>
      <link>/post/shell-operator/</link>
      <pubDate>Sat, 07 Nov 2020 11:29:44 +0800</pubDate>
      <guid>/post/shell-operator/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://github.com/flant/shell-operator#kubernetes&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;Shell Operator&lt;/code&gt;&lt;/a&gt; 是个冷僻又有点用的东西。这个工具的角度比较刁钻——使用的特定事件来触发 Shell 脚本。&lt;/p&gt;

&lt;p&gt;这个工具的使用方式也很有趣，要定义某个任务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;编写任务脚本&lt;/li&gt;
&lt;li&gt;使用它的基础镜像，加入任务脚本，打包生成一个 Docker 镜像并推送&lt;/li&gt;
&lt;li&gt;把镜像部署到集群之中&lt;/li&gt;
&lt;li&gt;对应事件出现时，将执行该任务脚本。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;触发方法&#34;&gt;触发方法&lt;/h2&gt;

&lt;p&gt;Shell Operator 支持多种事件触发，包括 Kubernetes、启动时执行以及定期执行。&lt;/p&gt;

&lt;p&gt;定期执行方式可以使用类似 Crontab 的语法，定义特定时间内执行指定脚本。&lt;/p&gt;

&lt;p&gt;启动时执行只会执行一次。&lt;/p&gt;

&lt;p&gt;Kubernetes 事件触发条件和 Admission Webhook 类似，但是更加细致，支持多种对象的 &lt;code&gt;Added&lt;/code&gt;、&lt;code&gt;Modified&lt;/code&gt;、&lt;code&gt;Deleted&lt;/code&gt; 操作。可以根据名称、标签、特定字段、命名空间进行过滤，还可以使用 jq 语法进行进一步的过滤。&lt;/p&gt;

&lt;h2 id=&#34;写个脚本&#34;&gt;写个脚本&lt;/h2&gt;

&lt;p&gt;项目 &lt;code&gt;README.md&lt;/code&gt; 就提供了一个简单的例子，大致体现了 Shell Operator 的功能和用法。&lt;/p&gt;

&lt;p&gt;首先生成一个脚本文件，并设置权限为可执行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/usr/bin/env bash

if [[ $1 == &amp;quot;--config&amp;quot; ]] ; then
  cat &amp;lt;&amp;lt;EOF
configVersion: v1
kubernetes:
- apiVersion: v1
  kind: Pod
  executeHookOnEvent: [&amp;quot;Added&amp;quot;]
EOF
else
  podName=$(jq -r .[0].object.metadata.name $BINDING_CONTEXT_PATH)
  echo &amp;quot;Pod &#39;${podName}&#39; added&amp;quot;
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个小脚本体现了 Hook 的基本用法，如果使用 &lt;code&gt;--config&lt;/code&gt; 参数启动，则会输出一个 YAML 格式的配置文件，其中表明该脚本会在 Kubernetes 中加入新的 Pod 时候触发。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;configVersion: v1
kubernetes:
- apiVersion: v1
  kind: Pod
  executeHookOnEvent: [&amp;quot;Added&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在无参数启动时，则会运行这个 Hook 的主逻辑，在 Hook 被触发时，会把触发时的上下文内容以 JSON 的形式保存到一个文件，文件名会保存到 &lt;code&gt;$BINDING_CONTEXT_PATH&lt;/code&gt;。这里会用 &lt;code&gt;jq&lt;/code&gt; 从文件的 JSON 中获取 Pod 名称，并展示出来。&lt;/p&gt;

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

&lt;p&gt;项目提供了一个基础镜像 &lt;code&gt;flant/shell-operator:latest&lt;/code&gt;，其中内置了 &lt;code&gt;bash&lt;/code&gt;、&lt;code&gt;kubectl&lt;/code&gt;、&lt;code&gt;jq&lt;/code&gt; 以及 &lt;code&gt;shell-operator&lt;/code&gt; 的可执行文件。把脚本加入镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM flant/shell-operator:latest
ADD pods-hook.sh /hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;构建镜像并推送到镜像库之后，为了让任务正常运行，要给它创建命名空间以及具备合适权限的 &lt;code&gt;ServiceAccount&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create namespace example-monitor-pods
...
$ kubectl create serviceaccount monitor-pods-acc \
    --namespace example-monitor-pods
...
$ kubectl create clusterrole monitor-pods \
    --verb=get,watch,list --resource=pods
...
$ kubectl create clusterrolebinding monitor-pods \
    --clusterrole=monitor-pods \
    --serviceaccount=example-monitor-pods:monitor-pods-acc
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;monitor-pods-acc&lt;/code&gt; 具备了对 &lt;code&gt;Pod&lt;/code&gt; 对象进行 &lt;code&gt;get&lt;/code&gt;、&lt;code&gt;watch&lt;/code&gt; 以及 &lt;code&gt;list&lt;/code&gt; 操作的能力。我们用这个 &lt;code&gt;ServiceAccount&lt;/code&gt; 以及前面生成的镜像，生成一个 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: shell-operator
  namespace: example-monitor-pods
  annotations:
    prometheus.io/scrape: &#39;true&#39;
    prometheus.io/port: &#39;9115&#39;
spec:
  containers:
  - name: shell-operator
    image: dustise/shell-operator:monitor-pods
    imagePullPolicy: Always
  serviceAccountName: monitor-pods-acc
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;触发-hook&#34;&gt;触发 Hook&lt;/h2&gt;

&lt;p&gt;这个 Pod 启动之后，新建一个 Deployment，再看 Hook 的日志：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create deployment sleep  --image=dustise/sleep:v0.9.5
deployment.apps/sleep created
$ kubectl logs -f shell-operator  -n example-monitor-pods
...
{&amp;quot;binding&amp;quot;:&amp;quot;kubernetes&amp;quot;,&amp;quot;event&amp;quot;:&amp;quot;kubernetes&amp;quot;,&amp;quot;hook&amp;quot;:&amp;quot;pods-hook.sh&amp;quot;,&amp;quot;level&amp;quot;:&amp;quot;info&amp;quot;,&amp;quot;msg&amp;quot;:&amp;quot;Pod &#39;sleep-84d5994d88-qnc7c&#39; added&amp;quot;,&amp;quot;output&amp;quot;:&amp;quot;stdout&amp;quot;,&amp;quot;queue&amp;quot;:&amp;quot;main&amp;quot;,&amp;quot;task&amp;quot;:&amp;quot;HookRun&amp;quot;,&amp;quot;time&amp;quot;:&amp;quot;2020-11-07T15:46:21Z&amp;quot;}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;监控信息&#34;&gt;监控信息&lt;/h2&gt;

&lt;p&gt;细心的读者会看到，前面的 Pod 包含了一个 Prometheus 的注解，如果打开 Prometheus 就会看到其中多出了一系列 &lt;code&gt;shell_operator_hook&lt;/code&gt; 前缀的监控指标，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;shell_operator_tasks_queue_action_duration_seconds_bucket{queue_action=&amp;quot;AddLast&amp;quot;,queue_name=&amp;quot;main&amp;quot;,le=&amp;quot;0&amp;quot;} 0
shell_operator_tasks_queue_action_duration_seconds_bucket{queue_action=&amp;quot;AddLast&amp;quot;,queue_name=&amp;quot;main&amp;quot;,le=&amp;quot;1e-06&amp;quot;} 0
shell_operator_tasks_queue_action_duration_seconds_bucket{queue_action=&amp;quot;AddLast&amp;quot;,queue_name=&amp;quot;main&amp;quot;,le=&amp;quot;2e-06&amp;quot;} 0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;并且，Shell Operator 还支持加入自定义的监控指标，只要将指标写入文件，并把文件名保存到 &lt;code&gt;$METRICS_PATH&lt;/code&gt; 环境变量之中就可以。&lt;/p&gt;

&lt;h2 id=&#34;用途&#34;&gt;用途&lt;/h2&gt;

&lt;p&gt;这东西非常适合急救——在一些特殊场合，根据特定条件，对指定对象进行一些操作，例如注解、标签、清除、重启等。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>做个 openEuler 的系统镜像</title>
      <link>/post/openeuler-docker-image/</link>
      <pubDate>Thu, 05 Nov 2020 14:26:49 +0800</pubDate>
      <guid>/post/openeuler-docker-image/</guid>
      <description>

&lt;p&gt;前几天突然想知道，操作系统镜像是怎么搞的。放狗搜了一下，发现&lt;a href=&#34;https://docs.docker.com/develop/develop-images/baseimages/&#34; target=&#34;_blank&#34;&gt;官网&lt;/a&gt;提供了一些这方面的介绍，看来很轻松，结合以前翻译的&lt;a href=&#34;https://blog.fleeto.us/post/how-are-docker-images-built/&#34; target=&#34;_blank&#34;&gt;镜像是怎样炼成的&lt;/a&gt;，一时手痒，就想用菊厂操作系统新秀 openEuler 练练手——恩是 openEuler 不是那啥。&lt;/p&gt;

&lt;p&gt;根据 Docker 官网介绍，几个流行操作系统都有自己的构建脚本，主要流程就是几个步骤：&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;/ul&gt;

&lt;p&gt;openEuler 安装之后，可以看到是个 Yum 系的系统，所以可以参考一下 &lt;a href=&#34;https://github.com/moby/moby/blob/master/contrib/mkimage-yum.sh&#34; target=&#34;_blank&#34;&gt;CentOS 的脚本&lt;/a&gt;，粗看上来，依赖并不复杂，&lt;code&gt;yum&lt;/code&gt;、&lt;code&gt;docker&lt;/code&gt; 以及 &lt;code&gt;tar&lt;/code&gt;。撸起袖子开工就是了。&lt;/p&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;在 openEuler 官网下载 ISO 文件：&lt;code&gt;https://openeuler.org/en/download/&lt;/code&gt;。这里我选择了 LTS 的最小化版本。使用 Parallels Desktop 安装虚拟机，安装之后对几个依赖进行验证。&lt;/p&gt;

&lt;p&gt;首先发现这个系统可能因为某些原因并没有内置 Repo 源，个人用户自然无需担心这个问题，在 &lt;code&gt;/etc/yum.repos.d&lt;/code&gt; 中加入软件源：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[openeuler]
name=openEuler
baseurl=https://repo.openeuler.org/openEuler-20.03-LTS/OS/x86_64/
enabled=1
gpgcheck=0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Docker 的安装也可以使用 CentOS 的源：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[docker]
name=Docker CE Stable - $basearch
baseurl=https://download.docker.com/linux/centos/7/$basearch/stable
enabled=1
gpgcheck=0

[extra]
name=Extra
baseurl=http://mirror.centos.org/centos/7/extras/x86_64
enabled=1
gpgcheck=0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中的 Extra 库来自 CentOS，用于满足一些 Docker 的安装依赖。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yum install docker-ce docker-ce-cli containerd.io --nobest&lt;/code&gt; 安装 Docker，之后就可以运行 部署脚本了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ ./mkimage-yum.sh
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不过虚拟机下运行成功并不是这么容易的，这个脚本的运行会在 &lt;code&gt;/tmp&lt;/code&gt; 中运行，大概需要 900MB 的磁盘空间，和 40k 左右的 inode。建议运行之前使用 &lt;code&gt;df -h -i&lt;/code&gt; 查看一下 &lt;code&gt;/tmp&lt;/code&gt; 的可用情况。openEuler 的缺省 &lt;code&gt;/tmp&lt;/code&gt; 较小，可以使用 &lt;code&gt;mount -o remount,size=15G /tmp/&lt;/code&gt; 调整。&lt;/p&gt;

&lt;p&gt;如果一切正常的话，会看到在大量的错误信息之后，看到一行输出：&lt;code&gt;success&lt;/code&gt;。这是脚本在生成镜像 TAR 文件，使用 Docker 加载并运行之后输出的。可以查看一下这个镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
openeuler           20.03               fe7ddc25c484        5 hours ago         1.57GB

$ docker run -it openeuler:20.03 bash

Welcome to 4.19.90-2003.4.0.0036.oe1.x86_64

System information as of time:  Thu Nov  5 08:56:29 UTC 2020

System load:    0.02
Processes:      5
Memory used:    7.3%
Swap used:      0.9%
Usage On:       9%
IP address:     172.17.0.2
Users online:   0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;另外，如果仔细点看上面提供的 YUM Repoistory，会发现一个&lt;a href=&#34;https://repo.openeuler.org/openEuler-20.03-LTS/docker_img/x86_64/&#34; target=&#34;_blank&#34;&gt;神奇的文件夹&lt;/a&gt;，其中包含了 x86 和 aarch64 两个架构的原厂镜像_所以本文仅供学习交流，不建议用于商业用途，请于 24 小时内忘掉。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Base Image&lt;/strong&gt;：&lt;code&gt;https://docs.docker.com/develop/develop-images/baseimages/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构建脚本&lt;/strong&gt;：&lt;code&gt;https://github.com/moby/moby/blob/master/contrib/mkimage-yum.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下载 ISO&lt;/strong&gt;：&lt;code&gt;https://openeuler.org/en/download/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下载镜像&lt;/strong&gt;：&lt;code&gt;https://repo.openeuler.org/openEuler-20.03-LTS/docker_img/x86_64/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>节省镜像库空间的一个思路</title>
      <link>/post/which-image-should-be-removed/</link>
      <pubDate>Sun, 01 Nov 2020 19:33:05 +0800</pubDate>
      <guid>/post/which-image-should-be-removed/</guid>
      <description>

&lt;p&gt;最近遇到一个有趣的状况，某镜像仓库占用了大量的磁盘空间。通常要解决这种问题，给 Registry 发删除指令，并进行 GC 就可以了。然而很多时候，所有镜像都正常，在删除多个 Tag 甚至是 Repository 之后，问题仍然没能缓解，原理也很容易理解——删除的镜像虽然大，可能只是复用了一些比较大的层，删除镜像并不会真正的发出，所以还是需要对镜像库的存储进行更多的了解，进行进一步的统计，在层一级对镜像仓库进行分析，才能获取更有效的途径。&lt;/p&gt;

&lt;h2 id=&#34;docker-registry-exporter&#34;&gt;Docker Registry Exporter&lt;/h2&gt;

&lt;p&gt;首先发现了一个有意思的项目：&lt;a href=&#34;https://github.com/sky-uk/docker-registry-exporter&#34; target=&#34;_blank&#34;&gt;DockerRegistryExporter&lt;/a&gt;，这个项目是一个 Python 编写的 Prometheus Exporter，其中包含四个 Gauge：&lt;/p&gt;

&lt;p&gt;-&lt;code&gt;repository_tags_total&lt;/code&gt;：按镜像计算的 Tag 数量。
-&lt;code&gt;repository_revisions_total&lt;/code&gt;：按镜像计算的版本数量。
-&lt;code&gt;repository_tag_layers_total&lt;/code&gt;：以镜像和 Tag 计算的 Layer 数量。
-&lt;code&gt;repository_tag_size_bytes&lt;/code&gt;：以镜像和 Tag 计算的文件尺寸。&lt;/p&gt;

&lt;p&gt;该镜像使用挂卷的方式，直接对镜像库文件系统进行扫描，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;containers:
- image: registry:2
  name: registry
  ports:
  - containerPort: 5000
    name: http
    protocol: TCP
  readinessProbe:
    httpGet:
      path: /
      port: 5000
    initialDelaySeconds: 1
    timeoutSeconds: 1
  livenessProbe:
    httpGet:
      path: /
      port: 5000
    initialDelaySeconds: 1
    timeoutSeconds: 1
  volumeMounts:
  - name: storage
    mountPath: /var/lib/registry

- image: skyuk/docker-registry-exporter:v1.0.0
  name: registry-exporter
  args:
    - /var/lib/registry/docker/registry/v2
  ports:
  - containerPort: 8080
    name: http
    protocol: TCP
  volumeMounts:
  - name: storage
    mountPath: /var/lib/registry

volumes:
- name: storage
  persistentVolumeClaim:
    claimName: registry
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;通过Sidecar的部署方式和Registry容器共享文件系统，可以定时输出监控指标，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ curl http://registry:8080
# HELP repository_tag_size_bytes Size of eachtag
# TYPE repository_tag_size_bytes gauge
repository_tag_size_bytes{repository=&amp;quot;org/image1&amp;quot;, tag=&amp;quot;0.3.0&amp;quot;} 162749959.0
repository_tag_size_bytes{repository=&amp;quot;org/image2&amp;quot;, tag=&amp;quot;1009140546&amp;quot;} 226608092.0
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然而这并不能满足我的要求，关于引用的数据并没有体现，另外前面也提到，我们需要比较精确地获得镜像版本、Tag 和 Layer 之间的引用关系以及各自的尺寸，用 PromQL 有点别扭。&lt;/p&gt;

&lt;h2 id=&#34;我做了个奇怪的事情&#34;&gt;我做了个奇怪的事情&lt;/h2&gt;

&lt;p&gt;这并不是一个很常见的需求，只能是一个清理之前的准备动作，目前看来我需要找到的就是引用数量少、但是体量比较大的 Layer，但是谁知道以后会需要什么新的标准呢？干脆把这些东西写入到数据库里算了，把这些东西写入数据库之后，还掌握 SQL 这样传统才艺的程序员就可以随便搞一搞其它条件了。&lt;/p&gt;

&lt;h3 id=&#34;关于镜像仓库的一点基础&#34;&gt;关于镜像仓库的一点基础&lt;/h3&gt;

&lt;p&gt;镜像库根目录中有两个子目录：&lt;code&gt;blobs&lt;/code&gt; 中保存了所有的 Layer，而 &lt;code&gt;repositories&lt;/code&gt; 中则是以镜像为单位保存的元数据。&lt;/p&gt;

&lt;p&gt;首先看看镜像的数据&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ tree/org/repo/gameserver
.
├── revisions
│   └── sha256
│       └── ecfb0206e8b...
│           └── link
└── tags
    └── latest
        ├── current
        │   └── link
        └── index
            └── sha256
                └── ecfb020...
                    └── link
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;每个镜像的 Manifests 有两个目录，分别承载的是版本和 Tag，正常来说 Tag 和版本是一致的，但实际上在一些特别情况下，这两个数量可能是不一致的，就会导致只用 Tag 已经无法拉取该镜像，属于一种半孤立状态，应该说是需要清除的。&lt;/p&gt;

&lt;p&gt;两个目录中的&lt;code&gt;link&lt;/code&gt;文件中包含的是一个哈希码，可以使用这个哈希码在&lt;code&gt;_layers&lt;/code&gt;中查找到该镜像的版本/tag 对应的清单层，使用这个字符串可以在根&lt;code&gt;_layer&lt;/code&gt;中查到对应的目录，目录下面的&lt;code&gt;data&lt;/code&gt;文件中就是每个层的具体数据，对于清单层，其中会是一个&lt;code&gt;json&lt;/code&gt;字符串：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;schemaVersion&amp;quot;: 2,
    &amp;quot;mediaType&amp;quot;: &amp;quot;application/vnd.docker.distribution.manifest.v2+json&amp;quot;,
    &amp;quot;config&amp;quot;: {
        &amp;quot;mediaType&amp;quot;: &amp;quot;application/vnd.docker.container.image.v1+json&amp;quot;,
        &amp;quot;size&amp;quot;: 2694,
        &amp;quot;digest&amp;quot;: &amp;quot;sha256:7929bcd70e47d3726d55a870b2ca11c25792758f3ba8b4ff136811f0809af636&amp;quot;
    },
    &amp;quot;layers&amp;quot;: [
        {
            &amp;quot;mediaType&amp;quot;: &amp;quot;application/vnd.docker.image.rootfs.diff.tar.gzip&amp;quot;,
            &amp;quot;size&amp;quot;: 2546278,
            &amp;quot;digest&amp;quot;: &amp;quot;sha256:3db1cceb1cccb362634e914bfe76d329c64d148262a9e139a046337d82e1aeec&amp;quot;
        },
        {
            &amp;quot;mediaType&amp;quot;: &amp;quot;application/vnd.docker.image.rootfs.diff.tar.gzip&amp;quot;,
            &amp;quot;size&amp;quot;: 32,
            &amp;quot;digest&amp;quot;: &amp;quot;sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1&amp;quot;
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里看到清单中包含两个主节点，&lt;code&gt;config&lt;/code&gt; 和 &lt;code&gt;layer&lt;/code&gt;，至此，一个镜像是由三种不同的层构成的：清单、Config 和 Layer。我们关注的主要是 Layer，其中的 &lt;code&gt;data&lt;/code&gt; 文件包含的就是各层的具体内容，清单和 Config 中都是文本，Layer 通常都是二进制的，也是我们要关注的主要内容。&lt;/p&gt;

&lt;p&gt;接下来的问题就顺理成章了，把 Repository、Tag、Revision 以及 Layer 的关系建立起来，随便用个 SQL 语句，就能够按照具体需求对“引用少、尺寸大”的 Layer 进行过滤了。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Docker Registry Exporter&lt;/strong&gt;：&lt;code&gt;https://github.com/sky-uk/docker-registry-exporter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>用 Notary 和 OPA 在 Kubernetes 上使用内容签名</title>
      <link>/post/k8s-notary-and-opa/</link>
      <pubDate>Sat, 17 Oct 2020 01:18:58 +0800</pubDate>
      <guid>/post/k8s-notary-and-opa/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://medium.com/@siegert.maximilian/ensure-content-trust-on-kubernetes-using-notary-and-open-policy-agent-485ab3a9423c&#34; target=&#34;_blank&#34;&gt;Ensure Content Trust on Kubernetes using Notary and Open Policy Agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://medium.com/u/42543b7496a6?source=post_page-----485ab3a9423c--------------------------------&#34; target=&#34;_blank&#34;&gt;Daniel Geiger&lt;/a&gt; &lt;a href=&#34;https://medium.com/u/185afb909cc2?source=post_page-----485ab3a9423c--------------------------------&#34; target=&#34;_blank&#34;&gt;Maximilian Siegert&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;在 Kubernetes 上使用策略对部署行为进行限制，仅允许运行有签名的镜像。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们希望借助本文，让读者了解到如何在 Kubernetes 中使用可信镜像，其中依赖两个著名的 CNCF 开源项目：Notary 和 OPA。主要思路是使用 OPA 策略来定义自己的内容限制策略。&lt;/p&gt;

&lt;p&gt;主要内容如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;完成示例的先决条件&lt;/li&gt;
&lt;li&gt;Notary 和镜像信任的基本概念&lt;/li&gt;
&lt;li&gt;在 Kubernetes 上安装 Kubernetes&lt;/li&gt;
&lt;li&gt;OPA 和 Admission Control 的基本概念&lt;/li&gt;
&lt;li&gt;在 Kubernetes 上安装 OPA&lt;/li&gt;
&lt;li&gt;定义 Validating Admission Control 控制内容信任&lt;/li&gt;
&lt;li&gt;定义 Mutating Admission Control 完成自动化&lt;/li&gt;
&lt;li&gt;总结和展望&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果读者已经熟知 Notary 或者 OPA 的相关内容，可以跳过上述的两节基本概念部分。&lt;/p&gt;

&lt;h2 id=&#34;完成示例的先决条件&#34;&gt;完成示例的先决条件&lt;/h2&gt;

&lt;p&gt;如果要遵循后续的安装步骤，需要下列准备：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;如果是 Kubernetes 集群，至少启用了 &lt;code&gt;MutatingAdmissionWebhook&lt;/code&gt; 和&lt;code&gt;ValidatingAdmissionWebhook&lt;/code&gt;；如果是 Minikube，应该使用如下启动方式：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;$ minikube start \
--extra-config=apiserver.enable-admission-plugins=MutatingAdmissionWebhook,ValidatingAdmissionWebhook
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;私有镜像库，或者一个 Docker Hub ID，用于推送签名镜像。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;从&lt;a href=&#34;https://github.com/k8s-gadgets/k8s-content-trust&#34; target=&#34;_blank&#34;&gt;我们的 Github 仓库&lt;/a&gt;获取用于安装 OPA、Notary 以及 Notary-Wrapper 的 Helm Chart。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;notary-和镜像信任的基本概念&#34;&gt;Notary 和镜像信任的基本概念&lt;/h2&gt;

&lt;p&gt;将代码、可执行文件或者脚本进行签名，保障仅有受信内容才可运行，这是一个已知的最佳实践。软件签名不是什么新概念，有很多相关的供应商和方案，每个组织都有自己的方式来处理制品的签署和信任。然而如果把目光投向容器领域，可能会发现并没有那么多选择。&lt;/p&gt;

&lt;h3 id=&#34;notary-是什么&#34;&gt;Notary 是什么&lt;/h3&gt;

&lt;p&gt;你可能已经听说过 Notary，这是一个基于 &lt;a href=&#34;https://theupdateframework.github.io/&#34; target=&#34;_blank&#34;&gt;TUF 项目&lt;/a&gt;的用于软件制品签名的开源软件。&lt;/p&gt;

&lt;h3 id=&#34;notary-如何运作&#34;&gt;Notary 如何运作&lt;/h3&gt;

&lt;p&gt;首先说说 Notary 的核心概念。Notary 使用角色和元数据文件对受信集合内容进行签署，这些内容被称为全局唯一名称（GUN——Global Unique Name）。&lt;/p&gt;

&lt;p&gt;以 Docker 镜像为例，GUN 相当于 &lt;code&gt;[registry]/[repository name]:[tag]&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[registry]&lt;/code&gt; 是镜像的源仓库，&lt;code&gt;[repository name]&lt;/code&gt; 是镜像的名称。&lt;code&gt;[tag]&lt;/code&gt; 对镜像进行标记（通常代表版本）。&lt;/p&gt;

&lt;p&gt;Notary 借助 TUF 的角色和密钥层级关系对镜像进行签名。有五种密钥类型用于对元数据文件进行签署，并用 &lt;code&gt;.json&lt;/code&gt; 的方式保存到 &lt;a href=&#34;https://docs.docker.com/notary/service_architecture/&#34; target=&#34;_blank&#34;&gt;Notary 数据库&lt;/a&gt;。下图描述了密钥层级以及这些密钥的典型存储位置。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/key-hierarchy-in-notary.png&#34; alt=&#34;key-hierarchy-in-notary&#34; /&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;根密钥：每个 GUN 都有自己的根角色和密钥。根密钥是所有信任关系的基础，用于对根级元数据文件（其中包含根 ID、目标、快照以及时间戳公钥的 ID）进行签名。通常这个密钥是由（GUN）的属主管理的，并使用离线的方式进行保存（例如在本地目录或者硬件密钥设备）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;目标密钥：目标密钥负责签署目标元数据文件，其中包含该集合中的所有文件名、尺寸以及对应的哈希值。这个元数据文件用于对该仓库中的所有实际内容进行完整性验证。这还表示目标元数据文件包含了每个镜像标签的入口。目标密钥可以使用委托角色把信任关系委托给其它的合作者。目标密钥也是属于 GUN 属主的，同样用离线方式保存。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;委托密钥：如上文所说，目标密钥能够委托给其它角色。这些角色会有自己的密钥来签署被委托的元数据文件，其中同样会包含该集合中的文件名、尺寸以及对应的哈希。委托元数据文件能用于校验仓库中部分或者全部内容的完整性。这些密钥属于这个集合的协作者。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;快照密钥：快照密钥负责签署快照元数据文件，其中遍历了每个 GUN 的根、目标和委托元数据。这个元数据文件的目标就是验证其它元数据文件的完整性。快照密钥属于协作属主（本地），或者如果 Notary 服务（通过委托角色使用多个协作者）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;时间戳密钥：时间戳密钥用于签署时间戳元数据文件，这个密钥的存在目的是保障集合的时效性。这其中包含了元数据的最短过期时间、最近快照的文件名、尺寸以及哈希。这个元数据文件用来检验快照文件的完整性。时间戳密钥由 Notary 服务保存，这样这个密钥就能自动的根据服务器的请求自动重新生成。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;管理密钥的 Notary 服务架构包括两个组件：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Notary 服务器，用来保存和更新信任 GUN 的签署后元数据文件。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Notary Signer 保存了私钥，用于为 Notary Server 提供元数据签署能力。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Docker 文档中&lt;a href=&#34;https://docs.docker.com/notary/service_architecture/&#34; target=&#34;_blank&#34;&gt;这张 Notary 的示意图&lt;/a&gt;很好的概括了客户端与 Notary Server 以及 Signer 之间的通信。下图是一个简化版本：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/notary-server-signer-client.png&#34; alt=&#34;notary-server-signer-client.png&#34; /&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Notary 服务器可以使用 JWT Token 进行认证。如果没有使用这个功能，可以简单地上传新的元数据文件。如果客户端上传了新的元数据文件，Notary Server 会对老版本进行冲突检测，并对签名、校验和以及元数据的有效性进行检测。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;上传的元数据通过验证以后，Notary 服务器会生成时间戳元数据，并将元数据发给 Signer 进行签名。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Notary Signer 从数据库中获取加密的密钥，解密后对元数据进行签署。如果签署成功，则将签名发回给 Notary 服务器。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Notary Server 是所有受信集合（GUN）真实状态的来源，TUF 数据库中存储了客户端上传和服务器生成的元数据。生成的时间戳和快照元数据证明客户端上传的元数据是该可信集合的最新数据。Notary 服务器会通知客户其上传成功。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;客户端能够从服务器下载最新的元数据。Notary 服务器从数据库中取出元数据即可。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果时间戳过期，Notary 服务器会重新完成流程，生成新的时间戳，申请 Signer 签名，并在数据库中保存新签署的时间戳。然后发送新的时间戳以及用户请求的其它元数据。&lt;/p&gt;

&lt;p&gt;Notary 签署过程看起来很复杂，不过一个好消息就是，Docker 客户端中集成了用 Notary 签署镜像的能力。可以轻松地使用环境变量在本地设备上启用镜像信任机制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;DOCKER_CONTENT_TRUST=1&lt;/code&gt;：在客户端启用 Notary&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;DOCKER_CONTENT_TRUST_SERVER=”&amp;lt;url-to-your-Notary-server&amp;gt;”&lt;/code&gt;：使用自己的 Notary 服务提供信任关系&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;设置这些之后，Docker 客户端就会在拉取之前检查签名，并在推送之前请求签署凭据来对镜像进行签名。Docker HUB 还提供了自己的缺省 Notary 服务 &lt;code&gt;https://notary.docker.io&lt;/code&gt;，如果启用了内容信任，会用它对推送镜像进行签署。&lt;/p&gt;

&lt;p&gt;如果拉取镜像是有签名的，可以简单的使用 &lt;code&gt;docker trust inspect &amp;lt;GUN&amp;gt;&lt;/code&gt; 来检查签名情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ docker trust inspect nginx:latest
[
    {
        &amp;quot;Name&amp;quot;: &amp;quot;nginx:latest&amp;quot;,
        &amp;quot;SignedTags&amp;quot;: [
            {
                &amp;quot;SignedTag&amp;quot;: &amp;quot;latest&amp;quot;,
                &amp;quot;Digest&amp;quot;: &amp;quot;b2xxxxxxxxxxxxx4a0395f18b9f7999b768f2&amp;quot;,
                &amp;quot;Signers&amp;quot;: [
                    &amp;quot;Repo Admin&amp;quot;
                ]
            }
        ],
        &amp;quot;Signers&amp;quot;: [],
        &amp;quot;AdministrativeKeys&amp;quot;: [
            {
                &amp;quot;Name&amp;quot;: &amp;quot;Root&amp;quot;,
                &amp;quot;Keys&amp;quot;: [
                    {
                        &amp;quot;ID&amp;quot;: &amp;quot;d2fxxxxxxx042989d4655a176e8aad40d&amp;quot;
                    }
                ]
            },
            ...
        ]
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;除了使用 &lt;code&gt;docker trust&lt;/code&gt; 之外，也可以下载 &lt;a href=&#34;https://github.com/theupdateframework/notary/releases&#34; target=&#34;_blank&#34;&gt;Notary 客户端&lt;/a&gt;，直接和服务器进行通信。&lt;/p&gt;

&lt;h2 id=&#34;在-kubernetes-上安装-notary&#34;&gt;在 Kubernetes 上安装 Notary&lt;/h2&gt;

&lt;p&gt;到现在我们已经对 Notary 的工作机制有了个初步的认识。我们可以更进一步，在 Kubernetes 上安装自己的 Notary 服务。我们准备了两个 Shell 脚本和 Helm Chart，这样就可以很方便的进行安装了。开始之前请克隆我们的代码仓库：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ git clone https://github.com/k8s-gadgets/k8s-content-trust
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;安装&#34;&gt;安装&lt;/h3&gt;

&lt;p&gt;进入 &lt;code&gt;notary-k8s&lt;/code&gt; 目录。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;可选项目：构建 Notary 并加入自己的镜像库。
要从头构建最新的 Notary 镜像，需要从 &lt;code&gt;build&lt;/code&gt; 目录开始。如果要构建和推送 Notary 镜像到你自己的镜像仓库，可以编辑 &lt;code&gt;build.sh&lt;/code&gt; 文件，编辑 &lt;code&gt;REGISTRY&lt;/code&gt; 变量，使之匹配自己的镜像库，并执行 &lt;code&gt;build.sh&lt;/code&gt; 脚本。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bash build.sh
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来需要进入 &lt;code&gt;helm/notary&lt;/code&gt; 目录，并生成 TLS 证书，来确保和 Notary 服务通信的安全性：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ cd helm/notary
...
$ bash generateCerts.sh
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在准备好 Docker 镜像并把 TLS 证书写入 Chart 之后，就可以使用 Helm 在 Kubernetes 上进行部署了。另外也可以看看 &lt;code&gt;values.yaml&lt;/code&gt; 文件，修改一些必要的参数，例如缺省密码（&lt;code&gt;passwordalias1Name&lt;/code&gt;、 &lt;code&gt;passwordalias1Value&lt;/code&gt;）或者私有仓库。&lt;/p&gt;

&lt;p&gt;然后就是创建命名空间并安装 Helm Chart：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create namespace notary
# 切换到 notary 命名空间
$ helm install notary notary
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查镜像是否已经启动运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get pods –n notary
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果 Pod 已经运行，就表明 Notary 安装成功了。然而在我们试用 Notary 服务之前，我们应该提交最后生成的 Notary Wrapper 模板。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Notary Wrapper&lt;/code&gt; 是我们写的一个扩展，借助这个扩展，OPA 就能就能和 Notary 服务进行交互了。这是一个 CLI REST 界面，仅实现了获取已签名镜像哈希以及在服务上检查新人数据的功能。&lt;/p&gt;

&lt;p&gt;从 &lt;code&gt;notary-k8s/helm/certs&lt;/code&gt; 复制证书文件到 &lt;code&gt;helm/notary-wrapper/certs&lt;/code&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;notary-wrapper.crt&lt;/li&gt;
&lt;li&gt;notary-wrapper.key&lt;/li&gt;
&lt;li&gt;root-ca.crt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;进入源码的 &lt;code&gt;notary-wrapper&lt;/code&gt; 子目录。创建 OPA 命名空间并执行 Helm 安装过程。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create namespace opa
# switch to namespace opa
helm install notary-wrapper notary-wrapper
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;测试-notary&#34;&gt;测试 Notary&lt;/h2&gt;

&lt;p&gt;组件安装结束之后，就可以开始用我们的信任数据来测试 Notary 了，下图展示了这个过程：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/testing-notary.png&#34; alt=&#34;testing notary&#34; /&gt;&lt;/p&gt;

&lt;p&gt;我们需要签署一些本地镜像作为测试素材，所以首先从 Docker Hub 拉取一些镜像：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;如果你已经启用了 &lt;code&gt;DOCKER_CONTENT_TRUST&lt;/code&gt;，并且没有指定 &lt;code&gt;DOCKER_CONTENT_TRUST_SERVER&lt;/code&gt;，或者指定到了你的新服务器，拉取过程可能会失败。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;docker pull nginx:latest
docker pull busybox:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下一步就要连接我们的 Notary 客户端和服务器了：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;把 Notary 服务器加入 &lt;code&gt;/etc/hosts&lt;/code&gt;：&lt;code&gt;127.0.0.1 notary-server-svc&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在终端中打开第二个 Tab，并为 Notary Server 的 Pod 创建一个端口转发，以便本地使用：&lt;code&gt;kubectl port-forward notary-server-&amp;lt;...&amp;gt; 4443:4443&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;第一次要签名之前，要把你的 &lt;code&gt;root-ca.crt&lt;/code&gt; 从安装目录拷贝到你的 &lt;code&gt;.docker/tls&lt;/code&gt; 目录：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;mkdir -p $HOME/.docker/tls/notary-server-svc:4443
cp &amp;lt;...&amp;gt;/helm/notary/certs/root-ca.crt $HOME/.docker/tls/notary-server-svc:4443/
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;回到第一个终端 Tab，启用内容信任机制：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;export DOCKER_CONTENT_TRUST_SERVER=https://notary-server-svc:4443
export DOCKER_CONTENT_TRUST=1
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notary 已经启动，应该已经无法拉取任何没有被你的 Notary 服务签名的镜像了。不过可以打标签、签名和推送镜像（在我们的例子中，我们会简单的推送到我们自己的 Docker Hub 空间，使用的是我们自己的镜像签名）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;docker tag nginx:latest docker.io/&amp;lt;hub-id&amp;gt;/nginx:1 
docker push docker.io/&amp;lt;hub-id&amp;gt;/nginx:1
docker tag busybox:latest docker.io/&amp;lt;hub-id&amp;gt;busybox:1
docker push docker.io/&amp;lt;hub-id&amp;gt;/busybox:1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个推送命令会提示生成密码，用于请求签名密钥。这些步骤完成后，镜像会被推送到 Docker Hub，信任数据则会保存到 Notary Server。要进行校验，可以使用前面提到的 &lt;code&gt;docker trust inspect&lt;/code&gt; 命令，如果安装了 Notary 客户端，也可以用 &lt;code&gt;notary list&lt;/code&gt; 命令。命令执行结果类似：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ notary -s https://notary-server-svc:4443 --tlscacert $HOME/.docker/tls/notary-server-svc:4443/root-ca.crt list docker.io/&amp;lt;hub-id&amp;gt;/nginx
# output
NAME    DIGEST                                SIZE (BYTES)  ROLE
----    ------                                ------------  ----
1       cccef6d6bdea671c394954b0dxxxxxxxx     948           targets
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;如果必须重新部署 Notary，并使用新的密钥进行镜像签署，必须删除之前存储在 &lt;code&gt;.docker/tls&lt;/code&gt; 目录中保存的密钥。另外还需要删除 &lt;code&gt;.docker/trust/tuf&lt;/code&gt; 中现存的需要重新签署的镜像的信任数据。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;现在可以开始测试 Notary Wrapper。再新开一个终端 Tab，在 /etc/hosts 文件中加入该服务的地址：&lt;code&gt;127.0.0.1 notary-wrapper-svc&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;保存之后，对端口 4445 进行端口转发：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# switch to namespace opa
kubectl port-forward notary-wrapper-&amp;lt;...&amp;gt; 4445:4445
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;完成后就可以使用两个操作来检查 GUN、Tag 后者哈希的信任数据了，因为我们用的是 TLS 连接，要信任前面生成的根证书：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;把 GUN 和 Tag 数据提交给 &lt;code&gt;https://notary-wrapper-svc:4445/list&lt;/code&gt;，获取最新的镜像信任数据，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ curl -X POST https://notary-wrapper-svc:4445/list -H “Content-Type: application/json” -d ‘{“GUN”:”docker.io/&amp;lt;hub-id&amp;gt;/nginx”, “Tag”:”1&amp;quot;, “notaryServer”:”notary-server-svc.notary.svc:4443”}’ --cacert PATH/TO/YOUR/NOTARY/certs/root-ca.crt
# output - One item
{
    &amp;quot;Name&amp;quot;:&amp;quot;1&amp;quot;,
    &amp;quot;Digest&amp;quot;:&amp;quot;cccef6d6bdexxxxxx422&amp;quot;,
    &amp;quot;Size&amp;quot;:&amp;quot;948&amp;quot;,
    &amp;quot;Role&amp;quot;:&amp;quot;targets&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;把 GUN 和哈希码发送到 &lt;code&gt;https://notary-wrapper-svc:4445/verify&lt;/code&gt; 验证这个哈希对应的信任数据是否存在（返回码 200 或 404）。如果不知道哈希吗，可以使用 &lt;code&gt;docker inspect GUN:Tag&lt;/code&gt; 命令查看。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ curl -X POST https://notary-wrapper-svc:4445/verify -H “Content-Type: application/json” -d ‘{“GUN”:”docker.io/&amp;lt;hub-id&amp;gt;/nginx”, “SHA”:”&amp;lt;your-RepoDigest&amp;gt;”, “notaryServer”:”notary-server-svc.notary.svc:4443”}’ --cacert PATH/TO/YOUR/NOTARY/certs/root-ca.crt
...
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;后面会使用 Notary Wrapper 来实现内容信任。完成这个测试之后，就可以关闭端口转发，继续下面的内容了。&lt;/p&gt;

&lt;h3 id=&#34;在-kubernetes-上实施内容信任&#34;&gt;在 Kubernetes 上实施内容信任&lt;/h3&gt;

&lt;p&gt;现在我们已经可以签署镜像生成信任数据了，拼图还差最后一块——在 Kubernetes 上实施内容信任策略。这临门一脚的难处在于，Kubernetes 中并没有提供什么开关可以激活内容信任。&lt;/p&gt;

&lt;p&gt;又一个可能的方案就是依赖底层的 Docker 引擎，调用镜像验证插件，启用 &lt;code&gt;DOCKER_CONTENT_TRUST&lt;/code&gt;（可以参考这个 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/issues/30603#issuecomment-430889781&#34; target=&#34;_blank&#34;&gt;Issue&lt;/a&gt;），这种方法有两个弊端：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;集群节点需要依赖 Docker 引擎完成信任工作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;DOCKER_CONTENT_TRUST&lt;/code&gt; 是个非此即彼的开关，打开之后，无法拉取没有在 Notary 上签名的镜像。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;DOCKER_CONTENT_TRUST&lt;/code&gt; 只能检查一个镜像是否存在签名元数据，但是并不负责检查该签名是否属于这个 Tag。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为了克服几个弊端，我们把注意力放在了 Kubernetes Admission Control 上。&lt;/p&gt;

&lt;h2 id=&#34;opa-和-admission-control-的基本概念&#34;&gt;OPA 和 Admission Control 的基本概念&lt;/h2&gt;

&lt;p&gt;长话短说。Kubernetes Admission Controller 是一种插件机制，可以用来对集群上的资源进行校验和配置。它的作用包含在 Kubernetes API 请求的生命周期之中，除了内置的 30 个控制器（例如 &lt;a href=&#34;https://kubernetes.io/docs/concepts/policy/pod-security-policy/#enabling-pod-security-policies&#34; target=&#34;_blank&#34;&gt;PodSecurity Policy&lt;/a&gt;）之外，还会有使用自己的控制规则的需要。就可以创建自己的 Validating 或者 Mutating Webhook 了。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mutating&lt;/strong&gt;：这种 Webhook 会对请求对象进行变更，来满足特定的配置需求。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validating&lt;/strong&gt;：它可以对请求对象进行验证，拒绝验证失败的请求。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Admission Control 触发的顺序是非常重要的知识点：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/kube-api-lifecycle.png&#34; alt=&#34;Kubernetes API Lifecycle&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 会首先执行 Mutating 过程，然后才是进行验证。这样就能确保被变更过的请求对象能够正确地被校验。OPA 就是最好的实现 Mutaiting 和 Validating Webhook 的方法之一。&lt;/p&gt;

&lt;h3 id=&#34;什么是-opa&#34;&gt;什么是 OPA&lt;/h3&gt;

&lt;p&gt;OPA 是一个通用的策略引擎，它使用一种高级的声明式语言（Rego）编写策略。下图展示了 OPA 集成到 Kubernetes API 生命周期的形式：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/api-with-opa.png&#34; alt=&#34;api-with-opa&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;在-kubernetes-上安装-opa&#34;&gt;在 Kubernetes 上安装 OPA&lt;/h2&gt;

&lt;p&gt;我们希望在 Kubernetes 上借助 OPA/Rego 的弹性策略实现内容信任机制。然而在开始之前，首先要在集群上部署 OPA。&lt;/p&gt;

&lt;p&gt;假设你已经有了符合条件的集群，在完成命名空间创建和 Notary 步骤之后，就可以开始进入仓库中的 OPA 目录开始安装了。&lt;/p&gt;

&lt;p&gt;Kubernetes 和 OPA 之间的通信必须是 TLS 加密的，因此需要给 OPA 创建额外的证书和密钥。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# copy the root-ca
cp ~/PATH/TO/k8-content-trust/notary-k8s/helm/notary/certs/root-ca.crt ~/PATH/TO/k8-content-trust/open-policy-agent/helm/opa/certs
# generate the additional OPA certs 
cd helm/opa
bash generateCerts.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;OPA 在安装后是自动生效的，因此应该排除一些命名空间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;kubectl label ns kube-system openpolicyagent.org/webhook=ignore
kubectl label ns opa openpolicyagent.org/webhook=ignore
kubectl label ns notary openpolicyagent.org/webhook=ignore
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来我们要确认一下 &lt;code&gt;values.yaml&lt;/code&gt; 中的 &lt;code&gt;validating&lt;/code&gt; 和 &lt;code&gt;mutating&lt;/code&gt; 是否已经配置（晚些时候我们会设置 &lt;code&gt;mutating: true&lt;/code&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# open-policy-agent/helm/opa/values.yml
...
validating: true
mutating: false
...
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# switch to namespace opa
helm upgrade --install opa opa
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在安装结束之后，可以在终端打开一个新 Tab，会看到 OPA 日志中 API Server 的进入请求。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# ctrl-c to exit
kubectl logs -n opa -f opa-deploy-&amp;lt;...&amp;gt; opa
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;定义-validating-admission-control-控制内容信任&#34;&gt;定义 Validating Admission Control 控制内容信任&lt;/h2&gt;

&lt;p&gt;总算到了有意思的部分了，开始实现内容信任机制。Notary 和 OPA 都已整装待发，首先我们想拒绝一切不受信任的镜像。要完成这个任务，要先搞清楚 Docker Tag 和哈希之间的关系。&lt;/p&gt;

&lt;p&gt;一般来说，我们会使用 GUN 以及标签来部署镜像。然而多数人会忽略一个事实，镜像标签是可以覆盖的，因此它的唯一性是靠不住的。一个集合的所有者能够用同样的 Tag 多次推送变更了的已签署镜像。为了避免这种情况，应该使用唯一摘要进行镜像拉取。&lt;/p&gt;

&lt;p&gt;我们定义两条 Rego 规则来完成这个 Webhook：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;拒绝只使用普通 Tag (包括 &lt;code&gt;latest&lt;/code&gt;)的部署。&lt;/li&gt;
&lt;li&gt;拒绝使用了哈希但是没有被 Notary 签名的镜像。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;已经随 Helm 安装好。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;先看看第一条规则（&lt;code&gt;helm/opa/policy/validating/rules.rego&lt;/code&gt;）&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-rego&#34;&gt;package policy.validating

operations := {&amp;quot;CREATE&amp;quot;, &amp;quot;UPDATE&amp;quot;}

kind := {&amp;quot;Pod&amp;quot;, &amp;quot;Deployment&amp;quot;}

# rule to deny digests for pods and deployments
deny[msg] {
  operations[input.request.operation]
  kind[input.request.kind.kind]
  image = get_images[_]
  not contains(image.name, &amp;quot;@sha256:&amp;quot;)
  msg := sprintf(&amp;quot;%v contains tag; only images with checksum are allowed&amp;quot;, [image.name])
}

# rule deny if digest is not in notary
deny[msg] {
  operations[input.request.operation]
  kind[input.request.kind.kind]
  image = get_images[_]
  contains(image.name, &amp;quot;@sha256:&amp;quot;)

  # Example to mock digest comparison
  # parts := split_image(image.name)
  # not parts.digest == &amp;quot;@sha256:50&amp;quot;

  get_checksum_status(image.name) != 200
  msg := sprintf(&amp;quot;No trust data found for the following image: %v &amp;quot;, [image.name])
}

# helper rules
# get images if pod
get_images[x] {
  input.request.kind.kind == &amp;quot;Pod&amp;quot;
  name := input.request.object.spec.containers[i].image
  x := {
    &amp;quot;index&amp;quot;: i,
    &amp;quot;name&amp;quot;: name,
  }
}

## get images if deployment
get_images[x] {
  input.request.kind.kind == &amp;quot;Deployment&amp;quot;
  name := input.request.object.spec.template.spec.containers[i].image
  x := {
    &amp;quot;index&amp;quot;: i,
    &amp;quot;name&amp;quot;: name,
  }
}

# rule to split gun and tag
split_image(image) = x {
  parts := split(image, &amp;quot;@sha256:&amp;quot;)
  x := {
    &amp;quot;gun&amp;quot;: parts[0],
    &amp;quot;digest&amp;quot;: parts[1],
  }
}

# rule to get digest from notary-wrapper
get_checksum_status(image) = status {
  wrapperRootCa := &amp;quot;/etc/certs/notary/root-ca.crt&amp;quot;
  notaryWrapperURL = &amp;quot;https://notary-wrapper-svc.opa.svc:4445/verify&amp;quot;
  parts := split_image(image)
  body := {
    &amp;quot;GUN&amp;quot;: parts.gun,
    &amp;quot;SHA&amp;quot;: parts.digest,
    &amp;quot;notaryServer&amp;quot;: &amp;quot;notary-server-svc.notary.svc:4443&amp;quot;,
  }

  headers_json := {&amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;}
  output := http.send({&amp;quot;method&amp;quot;: &amp;quot;post&amp;quot;, &amp;quot;url&amp;quot;: notaryWrapperURL, &amp;quot;headers&amp;quot;: headers_json, &amp;quot;body&amp;quot;: body, &amp;quot;tls_ca_cert_file&amp;quot;: wrapperRootCa})
  status := output.status_code
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的规则会检查尝试创建或更新 Pod 或者 Deployment 类型的 API 请求。&lt;/p&gt;

&lt;p&gt;根据资源类型，&lt;code&gt;get_image[x]&lt;/code&gt; 规则会确保遍历请求中的所有容器，检查这些容器是否用摘要（例如 &lt;code&gt;[GUN]@sha256:[digest hash]&lt;/code&gt;）进行拉取。&lt;/p&gt;

&lt;p&gt;因此简单的检查一下，镜像是否用了 &lt;code&gt;@sha256&lt;/code&gt; 就可以了。否则我们会认为此次尝试部署的是一个用 Tag 标识的镜像。如果这一规则被触发，请求就会被阻拦，并得到返回的错误消息。&lt;/p&gt;

&lt;p&gt;接下来我们继续定义第二个规则，拒绝没有被 Notary 信任的摘要。&lt;/p&gt;

&lt;p&gt;在这个规则里，我们在 &lt;code&gt;get_checksum_status(image)&lt;/code&gt; 中用了 OPA 中集成的 &lt;code&gt;http.send&lt;/code&gt; 函数。首先会从请求中获取每个镜像的哈希，然后在 &lt;code&gt;get_checksum_status(image)&lt;/code&gt; 中发送镜像的 GUN 和摘要到 Notary Wrapper，Notary Wrapper 会检查每个镜像是否都已签名。如果请求返回的不是 200，那么部署动作会被制止。&lt;/p&gt;

&lt;p&gt;简单说 &lt;code&gt;http.send&lt;/code&gt; 函数在目标不可用时不会返回响应（可以参考 OPA 的一个&lt;a href=&#34;https://github.com/open-policy-agent/opa/issues/2187&#34; target=&#34;_blank&#34;&gt;功能申请&lt;/a&gt;）。在我们这里因为有了 Notary Wrapper，只要它正常工作，就不会遇到这个困扰。然而一旦 Notary Wrapper 不可用，OPA 也会故障，会被 &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt; 中的 &lt;code&gt;failurePolicy: Fail&lt;/code&gt; 定义所捕获。&lt;/p&gt;

&lt;p&gt;上面描述的两条规则就足以在 Kubernetes 集群中完成对内容信任的控制了。&lt;/p&gt;

&lt;p&gt;要进行测试，只需要简单的部署一个新的 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# trust-pinning-test
apiVersion: v1
kind: Pod
metadata:
  name: trust-pinning-test
  namespace: default
spec:
  containers:
  # trigger rule 1:
  - image: GUN/&amp;lt;hub-id&amp;gt;/nginx:1
  # trigger rule 2:
  # - image: GUN/&amp;lt;hub-id&amp;gt;/nginx@sha256:89cce606b29fb2xxxxx
  # valid deployment:
  # - image: GUN/&amp;lt;hub-id&amp;gt;/nginx@sha256:&amp;lt;your-signed-RepoDigest&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;另外在 &lt;code&gt;open-policy-agent/tests&lt;/code&gt; 中还包含了多个针对不同需求的过个测试。&lt;/p&gt;

&lt;p&gt;接下来的示意图展示了我们目前的工作成果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/opa-image-verification.png&#34; alt=&#34;opa-image-verification&#34; /&gt;&lt;/p&gt;

&lt;p&gt;每次部署都会发出 API 请求，随即开始校验过程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;请求触发了校验 Webhook，发起对 OPA 的调用。&lt;/li&gt;
&lt;li&gt;OPA 会检查镜像的拉取方式，如果使用的是摘要方式，就会向 Notary Wrapper 请求信任数据。Notary Wrapper 则会从 Notary 服务器查询数据，并返回给 OPA，OPA 据此进行决策。如果没有触发规则，Kubernetes 会继续部署。&lt;/li&gt;
&lt;li&gt;根据哈希从镜像库拉取（本例中是 DockerHub）。&lt;/li&gt;
&lt;li&gt;部署 Pod。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;到此为止，我们已经成功的实现了内容信任机制。然而查询 &lt;code&gt;RepoDigests&lt;/code&gt; 是个很麻烦的事情。如果能基于 Tag 使用内容信任就两全其美了。&lt;/p&gt;

&lt;h2 id=&#34;定义-mutating-admission-control-完成自动化&#34;&gt;定义 Mutating Admission Control 完成自动化&lt;/h2&gt;

&lt;p&gt;Mutating Webhook 是用于在校验之前对请求内容进行变更的，我们接下来会编写这样一个功能。每次用户尝试部署一个带标签的镜像时，就启动 Webhook，自动将镜像引用改为哈希模式。大致工作流程如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/mutation-pod-deploy.png&#34; alt=&#34;mutation-pod-deploy&#34; /&gt;&lt;/p&gt;

&lt;p&gt;API 请求流经 Webhook：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;如果请求中包含 Pod，操作类型是创建或者更新，并且镜像是用 Tag 标识的，就会触发 OPA 的 Mutating Webhook（在所有的验证之前）。&lt;/li&gt;
&lt;li&gt;OPA 会用 Tag 去检查镜像，接下来 OPA 会为每个标签发起新的 &lt;code&gt;http.send&lt;/code&gt; 请求到 Notary Wrapper，向 Notary 服务器发起查询。&lt;/li&gt;
&lt;li&gt;如果 Notary Wrapper 在 Notary 服务器上找到了对应这个标签的条目，就会返回最新的 &lt;code&gt;RepoDigest&lt;/code&gt; 给 OPA，否则报错。&lt;/li&gt;
&lt;li&gt;OPA 对 Deployment 进行修改，把镜像标签更换为哈希，并把变更后的请求内容发送给 API Server。&lt;/li&gt;
&lt;li&gt;API Server 继续完成创建或更新流程，校验 Webhook 会对请求进行检查，如果请求有效，就用 &lt;code&gt;RepoDigest&lt;/code&gt; 从可信的仓库拉取镜像，并完成部署。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因为我们已经在安装过程中给 OPA 注册了 Mutating Webhook，我们只需要加入新的 Rego 规则就可以了。最简单的方式就是回到本地的 Helm 目录，启用 &lt;code&gt;mutating&lt;/code&gt;，然后执行 &lt;code&gt;helm upgrade&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# open-policy-agent/helm/opa/values.yml
...
validating: true
mutating: true
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# switch to namespace opa
helm upgrade --install opa opa
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;OPA 中的 Mutating Webhook 是 &lt;code&gt;main&lt;/code&gt; 方法的一部分，这个方法会在 API 请求时发起变更。&lt;code&gt;helm upgrade&lt;/code&gt; 会加入下面的新规则：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-rego&#34;&gt;package policy.mutating

import data.k8s.matches

main = {
  &amp;quot;apiVersion&amp;quot;: &amp;quot;admission.k8s.io/v1&amp;quot;,
  &amp;quot;kind&amp;quot;: &amp;quot;AdmissionReview&amp;quot;,
  &amp;quot;response&amp;quot;: response,
}

default uid = &amp;quot;missing-uid&amp;quot;

uid = input.request.uid

# default allow without patch
response = r {
  count(patch) == 0
  r := {
    &amp;quot;uid&amp;quot;: uid,
    &amp;quot;allowed&amp;quot;: true,
  }
}

# response with patch
response = {
  &amp;quot;uid&amp;quot;: input.request.uid,
  &amp;quot;allowed&amp;quot;: true,
  &amp;quot;patchType&amp;quot;: &amp;quot;JSONPatch&amp;quot;,
  &amp;quot;patch&amp;quot;: patch_bytes,
} {
  count(patch) &amp;gt; 0
  patch_json = json.marshal(patch)
  patch_bytes = base64url.encode(patch_json)
}

# patch
default patch = []

patch = result {
  operations := {&amp;quot;CREATE&amp;quot;, &amp;quot;UPDATE&amp;quot;}
  kind := {&amp;quot;Pod&amp;quot;, &amp;quot;Deployment&amp;quot;}
  
  
  operations[input.request.operation]
  kind[input.request.kind.kind]

  # construct patch for each image in the container array that requires it.
  result := [p |
    image = get_images[_]
    not contains(image.name, &amp;quot;@sha256:&amp;quot;)

    parts := split_image(image.name)

    # format: registry/project@sha256:xxx
    patchedImage := concat(&amp;quot;&amp;quot;, [parts.gun, &amp;quot;@sha256:&amp;quot;, get_digest(image.name)])

    # cconstruct JSON Patch for the deployment.
    # kube-apiserver expects changes to be represented as
    # JSON Patch operation against the resource.
    # the JSON Patch must be JSON serialized and base64 encoded.
    p := {
      &amp;quot;op&amp;quot;: &amp;quot;replace&amp;quot;,
      &amp;quot;path&amp;quot;: get_path(image.index),
      &amp;quot;value&amp;quot;: patchedImage,
    }
  ]
}

# helper rules

# rule to compute images set
# the first line ensures that its matched to the right k8s resource
# the second line iterates over each container and extracts the image
get_images[x] {
  input.request.kind.kind == &amp;quot;Pod&amp;quot;
  name := input.request.object.spec.containers[i].image
  x := {
    &amp;quot;index&amp;quot;: i,
    &amp;quot;name&amp;quot;: name,
  }
}

get_images[x] {
  input.request.kind.kind == &amp;quot;Deployment&amp;quot;
  name := input.request.object.spec.template.spec.containers[i].image
  x := {
    &amp;quot;index&amp;quot;: i,
    &amp;quot;name&amp;quot;: name,
  }
}

# construct and returns json path for &amp;quot;Pods&amp;quot;
get_path(index) = path {
  input.request.kind.kind == &amp;quot;Pod&amp;quot;
  path := concat(&amp;quot;/&amp;quot;, [&amp;quot;&amp;quot;, &amp;quot;spec&amp;quot;, &amp;quot;containers&amp;quot;, format_int(index, 10), &amp;quot;image&amp;quot;])
}

# construct and returns json path for &amp;quot;Deployment&amp;quot;
get_path(index) = path {
  input.request.kind.kind == &amp;quot;Deployment&amp;quot;
  path := concat(&amp;quot;/&amp;quot;, [&amp;quot;&amp;quot;, &amp;quot;spec&amp;quot;, &amp;quot;template&amp;quot;, &amp;quot;spec&amp;quot;, &amp;quot;containers&amp;quot;, format_int(index, 10), &amp;quot;image&amp;quot;])
}

split_image(image) = x {
  parts := split(image, &amp;quot;:&amp;quot;)
  x := {
    &amp;quot;gun&amp;quot;: parts[0],
    &amp;quot;tag&amp;quot;: parts[1],
  }
}

# helper rule to retrieve the digest from notary using notary-wrapper
get_digest(image) = digest {
  wrapperRootCa := &amp;quot;/etc/certs/notary/root-ca.crt&amp;quot;
  notaryWrapperURL = &amp;quot;https://notary-wrapper-svc.opa.svc:4445/list&amp;quot;
  parts := split_image(image)
  body := {
    &amp;quot;GUN&amp;quot;: parts.gun,
    &amp;quot;Tag&amp;quot;: parts.tag,
    &amp;quot;notaryServer&amp;quot;: &amp;quot;notary-server-svc.notary.svc:4443&amp;quot;
  }

  headers_json := {&amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;}
  output := http.send({&amp;quot;method&amp;quot;: &amp;quot;post&amp;quot;, &amp;quot;url&amp;quot;: notaryWrapperURL, &amp;quot;headers&amp;quot;: headers_json, &amp;quot;body&amp;quot;: body, &amp;quot;tls_ca_cert_file&amp;quot;: wrapperRootCa})
  digest := output.body.Digest
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;简单说一下这段代码的功能：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OPA 会使用 &lt;code&gt;response&lt;/code&gt; 规则中的代码加入需要的响应。&lt;/li&gt;
&lt;li&gt;第一个 &lt;code&gt;response&lt;/code&gt; 针对的是无需变更的请求，允许任意的 API 请求通过。&lt;/li&gt;
&lt;li&gt;第二个 &lt;code&gt;response&lt;/code&gt; 会调用 &lt;code&gt;patch&lt;/code&gt; 规则。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patch&lt;/code&gt; 规则会对任何面向 &lt;code&gt;Pod&lt;/code&gt; 或者 &lt;code&gt;Deployment&lt;/code&gt; 的 API 请求进行变更。结果参数首先会获取 API 请求中的镜像，检查是否每个镜像都是使用哈希进行拉取的（URL 中包含了 &lt;code&gt;@shar256:&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;如果不满足上一个条件，就会使用 &lt;code&gt;split_image&lt;/code&gt; 规则将镜像分为名称和标签两部分。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;split_image&lt;/code&gt; 返回的是一个数组，&lt;code&gt;get_digest&lt;/code&gt; 中使用这个数组调用 &lt;code&gt;http.send&lt;/code&gt; 函数通过 Notary Wrapper 向 Notary 请求哈希。如果 Notary 没有对应的哈希，会得到 404 的返回值。&lt;/li&gt;
&lt;li&gt;Kubernetes 中使用 &lt;code&gt;.json&lt;/code&gt; 格式的补丁。&lt;code&gt;.json&lt;/code&gt; 补丁（赋值给 &lt;code&gt;p&lt;/code&gt;）需要在 &lt;code&gt;path&lt;/code&gt; 参数中指定的路径上执行 &lt;code&gt;replace&lt;/code&gt; 操作，从而替换原有的拉取方式。在 Pod 和 Deployment 中，镜像字段的路径是不同的，我们需要创建两个 &lt;code&gt;get_digest&lt;/code&gt; 和  &lt;code&gt;get_path&lt;/code&gt; 来应对两种情况。&lt;/li&gt;
&lt;li&gt;OPA 会对补丁进行编码，并返回变更后的 API 请求给 API Server，继续后续操作。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果想要测试这个 Webhook，可以看看 &lt;code&gt;open-policy-agent/tests&lt;/code&gt;，如果保存了前面的校验 Webhook，可以测试一下有效和无效的 Tag 或者哈希。下表总结了 Webhook 的响应情况：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table.png&#34; alt=&#34;table&#34; /&gt;&lt;/p&gt;

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

&lt;p&gt;最终，我们成功地在 Kubernetes 集群上，无需改动部署习惯的情况下，实现了内容信任机制，除了这个，OPA 还能做很多其它的校验工作。&lt;/p&gt;

&lt;p&gt;我们知道这篇文章很长，但是我希望尽可能多地为读者提供更多细节。我们认为，虽然有很多的容器扫描和加固方面的技术，镜像签署和信任是目前容器安全方面的最大盲区之一。&lt;/p&gt;

&lt;p&gt;下一步需要做点什么呢？还有很多细节我们没能说明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：校验和变更过程的性能测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产就绪&lt;/strong&gt;：提供高可用的 Notary 部署，并把客户端（包括 Docker 客户端）做到硬件安全模块。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI-CD 集成&lt;/strong&gt;：在 CI/CD 中自动化地进行签名。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;感谢阅读全文，希望对你有所助益。这里尤其要感谢来自 OPA/Styra 的 Asad、Torin 以及 Jeff，对我们编写的规则作出很多支持。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Github 仓库：&lt;code&gt;https://github.com/k8s-gadgets/k8s-content-trust&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;TUF 项目：&lt;code&gt;https://theupdateframework.github.io/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Notary 数据库：&lt;code&gt;https://docs.docker.com/notary/service_architecture/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Notary 架构：&lt;code&gt;https://docs.docker.com/notary/service_architecture/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Notary 客户端：&lt;code&gt;https://github.com/theupdateframework/notary/releases&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Kubernetes 关于内容信任的讨论：&lt;code&gt;https://github.com/kubernetes/kubernetes/issues/30603#issuecomment-430889781&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>伸手党的容器镜像加固流程</title>
      <link>/post/harden-docker-image-flow-chart/</link>
      <pubDate>Tue, 15 Sep 2020 20:19:20 +0800</pubDate>
      <guid>/post/harden-docker-image-flow-chart/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;人在江湖飘，哪能不挨刀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;说了好几期 CIS 之类的运行时安全问题，K8s 在运行过程中，还有个常见的安全威胁就是镜像漏洞，不少同学都有在实施过程中因为镜像漏洞问题被吊打的经验，今天就结合个人经验，说说镜像漏洞修复的一般流程。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这里主要指的是第三方镜像的修复过程，Scratch 不在此列。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;开局一张图：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;graph TD
  start(修复任务) --&amp;gt; base_or_app{基础/应用}
  base_or_app--&amp;gt;|业务漏洞|official_image{官方镜像}
  base_or_app--&amp;gt;|基底漏洞|os_updated{底层更新}
  os_updated--&amp;gt;|是|reos_pass{底层扫描}
  os_updated--&amp;gt;|否|change_os(更换底层)
  change_os--&amp;gt;reos_pass
  reos_pass--&amp;gt;|已解决|can_build
  reos_pass--&amp;gt;|未解决|change_os
  official_image --&amp;gt; |是|image_updated{是否已更新?}
  official_image --&amp;gt; |否|can_build{源码齐全}
  image_updated --&amp;gt; |是|rescan_pass{重新扫描通过}
  rescan_pass --&amp;gt; |是|fin(结案)
  rescan_pass --&amp;gt; |否|start
  image_updated --&amp;gt;|否|can_build
  can_build --&amp;gt; |是|docker_build(docker build)
  can_build --&amp;gt; |否|docker_cp(docker cp/commit)
  docker_cp --&amp;gt; rescan_pass
  docker_build --&amp;gt; rescan_pass
  change_os --&amp;gt; rescan_pass
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;修复任务分析&#34;&gt;修复任务分析&lt;/h2&gt;

&lt;p&gt;通常扫描报告会明确指明确切的问题源头、相关软件包、问题版本、修复版本等，首先要根据报告判断修复的目标。修复目标并不一定是固定的，有些情况下可能仅需要修复公开的高危漏洞，有些可能要求更多。&lt;/p&gt;

&lt;p&gt;我们都知道，容器镜像是个分层结构，底层通常由操作系统（例如 &lt;code&gt;debian:buster-slim&lt;/code&gt;）或者特定运行时（例如 &lt;code&gt;openjdk:16&lt;/code&gt;）构成；另外可能构建应用程序，或者直接通过 &lt;code&gt;COPY&lt;/code&gt;/&lt;code&gt;ADD&lt;/code&gt; 的方式加入应用程序；另外还会通过 &lt;code&gt;CMD&lt;/code&gt;、&lt;code&gt;ENV&lt;/code&gt; 等指令设置运行环境等。软件漏洞多发于底层和应用程序层，因此这里需要根据扫描报告，确认我们的修复目标。&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;如果要修复的问题是处于应用层，就要判断该镜像是否为官方镜像，如果答案是肯定的，则可以直接更新官方镜像，通常同一个大版本下的小版本更新，都是兼容的，可以更新之后直接进行测试和复查。&lt;/p&gt;

&lt;p&gt;如果官方没有针对性的修复，或者镜像并未更新，就可能需要自己构建镜像了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;如果是开源软件，强烈建议提交 Issue 或者 PR&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;基础层漏洞&#34;&gt;基础层漏洞&lt;/h2&gt;

&lt;p&gt;如果是基础层漏洞，除了可以像应用层修复一样，检查版本更新之外，还有另一个选项，就是更换不同的基础层，例如从 Debian 更换为 Ubuntu，这种方式对于 all-in-one 形式的应用（例如大多数用 golang 构建的应用）尤其有效，如果应用程序依赖众多，就不合适了。另外众所周知，Alpine 和我们常用的发行版差异较大，因此也不太合适直接切换。&lt;/p&gt;

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

&lt;p&gt;是不是就一个 &lt;code&gt;docker build&lt;/code&gt; 就可以了？多数时候是的。不过要分成几种情况。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;有 &lt;code&gt;Dockerfile&lt;/code&gt; 的情况&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;官方已经发布二进制物料：这种情况通过修改 &lt;code&gt;Dockerfile&lt;/code&gt; 加入更新的二进制文件之后，执行 &lt;code&gt;docker build&lt;/code&gt; 即可。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;官方未发布二进制物料：这种就需要根据源码进行构建，然后再生成 Docker 镜像。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;没有 &lt;code&gt;Dockerfile&lt;/code&gt; 的情况&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;有的软件源码中通过 &lt;code&gt;Makefile&lt;/code&gt; 等方式提供了从二进制到镜像的构建方法，通常需要在 &lt;code&gt;README.md&lt;/code&gt; 或者 &lt;code&gt;BUILD.md&lt;/code&gt; 中查找线索。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;更换底层的情况，通常需要自己照猫画虎，重新编写 Dockerfile。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;还有一种比较尴尬的情况——有二进制物料，但是没 Dockerfile，这种我通常会使用 &lt;code&gt;docker cp&lt;/code&gt;-&amp;gt;&lt;code&gt;docker commit&lt;/code&gt; 的不入流方式。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;另外一种尴尬情况就是，官方只提供了 Docker 镜像，但是我们想要换掉基础层，这种情况和上面类似，用 &lt;code&gt;docker cp&lt;/code&gt; 把官方镜像中的应用文件复制出来即可。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;复测&#34;&gt;复测&lt;/h2&gt;

&lt;p&gt;在完成修复步骤之后，可以针对性地进行复测，查看修复情况，循环往复直到完成目标为止。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>kubectl 的奇技淫巧</title>
      <link>/post/tips-for-kubectl/</link>
      <pubDate>Thu, 10 Sep 2020 19:44:49 +0800</pubDate>
      <guid>/post/tips-for-kubectl/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://medium.com/flant-com/kubectl-commands-and-tips-7b33de0c5476&#34; target=&#34;_blank&#34;&gt;Ready-to-use commands and tips for kubectl&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://medium.com/@flant_com&#34; target=&#34;_blank&#34;&gt;Flant staff&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubectl 是 Kubernetes 最重要的命令行工具。在 Flant，我们会在 Wiki 和 Slack 上相互分享 Kubectl 的妙用（其实我们还有个搜索引擎，不过那就是另外一回事了）。多年以来，我们在 kubectl 方面积累了很多技巧，现在想要将其中的部分分享给社区。&lt;/p&gt;

&lt;p&gt;我相信很多读者对这些命令都非常熟悉；然而我还是希望读者能够从本文中有所获益，进而提高生产力。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;下列内容有的是来自我们的工程师，还有的是来自互联网。我们对后者也进行了测试，并且确认其有效性。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;现在开始吧。&lt;/p&gt;

&lt;h2 id=&#34;获取-pod-和节点&#34;&gt;获取 Pod 和节点&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;我猜你知道如何获取 Kubernetes 集群中所有 Namespace 的 Pod——使用 &lt;code&gt;--all-namepsaces&lt;/code&gt; 就可以。然而不少朋友还不知道，现在这一开关还有了 &lt;code&gt;-A&lt;/code&gt; 的缩写。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如何查找非 &lt;code&gt;running&lt;/code&gt; 状态的 Pod 呢？&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;kubectl get pods -A --field-selector=status.phase!=Running | grep -v Complete
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;顺便一说，&lt;code&gt;--field-selector&lt;/code&gt; 是个&lt;a href=&#34;https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/&#34; target=&#34;_blank&#34;&gt;值得深入&lt;/a&gt;一点的参数。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如何获取节点列表及其内存容量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;kubectl get no -o json | \
  jq -r &#39;.items | sort_by(.status.capacity.memory)[]|[.metadata.name,.status.capacity.memory]| @tsv&#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;获取节点列表，其中包含运行在每个节点上的 Pod 数量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;kubectl get po -o json --all-namespaces | \
  jq &#39;.items | group_by(.spec.nodeName) | map({&amp;quot;nodeName&amp;quot;: .[0].spec.nodeName, &amp;quot;count&amp;quot;: length}) | sort_by(.count)&#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;有时候 DaemonSet 因为某种原因没能在某个节点上启动。手动搜索会有点麻烦：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;$ ns=my-namespace
$ pod_template=my-pod
$ kubectl get node | grep -v \&amp;quot;$(kubectl -n ${ns} get pod --all-namespaces -o wide | fgrep ${pod_template} | awk &#39;{print $8}&#39; | xargs -n 1 echo -n &amp;quot;\|&amp;quot; | sed &#39;s/[[:space:]]*//g&#39;)\&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使用 &lt;code&gt;kubectl top&lt;/code&gt; 获取 Pod 列表并根据其消耗的 CPU 或 内存进行排序：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;# cpu
$ kubectl top pods -A | sort --reverse --key 3 --numeric
# memory
$ kubectl top pods -A | sort --reverse --key 4 --numeric
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;获取 Pod 列表，并根据重启次数进行排序：&lt;/p&gt;

&lt;p&gt;kubectl get pods &amp;ndash;sort-by=.status.containerStatuses[0].restartCount&lt;/p&gt;

&lt;p&gt;当然也可以使用 &lt;a href=&#34;https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#podstatus-v1-core&#34; target=&#34;_blank&#34;&gt;PodStatus&lt;/a&gt; 以及 &lt;a href=&#34;https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#containerstatus-v1-core&#34; target=&#34;_blank&#34;&gt;ContainerStatus&lt;/a&gt; 的其它字段进行排序。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;获取其它数据&#34;&gt;获取其它数据&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;运行 Ingress 时，经常要获取 Service 对象的 &lt;code&gt;selector&lt;/code&gt; 字段，用来查找 Pod。过去要打开 Service 的清单才能完成这个任务，现在使用 &lt;code&gt;-o wide&lt;/code&gt; 参数也可以：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl -n jaeger get svc -o wide
NAME                            TYPE        CLUSTER-IP        EXTERNAL-IP   PORT(S)                                  AGE   SELECTOR
jaeger-cassandra                ClusterIP   None              &amp;lt;none&amp;gt;        9042/TCP                                 77d   app=cassandracluster,cassandracluster=jaeger-cassandra,cluster=jaeger-cassandra
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如何输出 Pod 的 &lt;code&gt;requests&lt;/code&gt; 和 &lt;code&gt;limits&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;$ kubectl get pods -A -o=custom-columns=&#39;NAME:spec.containers[*].name,MEMREQ:spec.containers[*].resources.requests.memory,MEMLIM:spec.containers[*].resources.limits.memory,CPUREQ:spec.containers[*].resources.requests.cpu,CPULIM:spec.containers[*].resources.limits.cpu&#39;
NAME                                  MEMREQ       MEMLIM        CPUREQ   CPULIM
coredns                               70Mi         170Mi         100m     &amp;lt;none&amp;gt;
coredns                               70Mi         170Mi         100m     &amp;lt;none&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;kubectl run&lt;/code&gt;（以及 &lt;code&gt;create&lt;/code&gt;、&lt;code&gt;apply&lt;/code&gt;、&lt;code&gt;patch&lt;/code&gt;）命令有个厉害的参数 &lt;code&gt;--dry-run&lt;/code&gt;，该参数让用户无需真正操作集群就能观察集群的行为，如果配合 &lt;code&gt;-o yaml&lt;/code&gt;，就能输出命令对应的 YAML：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl run test --image=grafana/grafana --dry-run -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: test
  name: test
spec:
  replicas: 1
  selector:
    matchLabels:
      run: test
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;简单的把输出内容保存到文件，删除无用字段就可以使用了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1.18 开始 &lt;code&gt;kubectl run&lt;/code&gt; 生成的是 Pod 而非 Deployment。&lt;/p&gt;
&lt;/blockquote&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;获取指定资源的描述清单：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;kubectl explain hpa
KIND:     HorizontalPodAutoscaler
VERSION:  autoscaling/v1
DESCRIPTION:
     configuration of a horizontal pod autoscaler.
FIELDS:
   apiVersion    &amp;lt;string&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;网络&#34;&gt;网络&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;获取集群节点的内部 IP：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get nodes -o json | jq -r &#39;.items[].status.addresses[]? | select (.type == &amp;quot;InternalIP&amp;quot;) | .address&#39; | \
  paste -sd &amp;quot;\n&amp;quot; -
9.134.14.252
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;获取所有的 Service 对象以及其 &lt;code&gt;nodePort&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get -A svc -o json | jq -r &#39;.items[] | [.metadata.name,([.spec.ports[].nodePort | tostring ] | join(&amp;quot;|&amp;quot;))]| @tsv&#39;

kubernetes  null
...
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在排除 CNI（例如 Flannel）故障的时候，经常会需要检查路由来识别故障 Pod。Pod 子网在这里非常有用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get nodes -o jsonpath=&#39;{.items[*].spec.podCIDR}&#39; | tr &amp;quot; &amp;quot; &amp;quot;\n&amp;quot;                                                            fix-doc-azure-container-registry-config  ✭
10.120.0.0/24
10.120.1.0/24
10.120.2.0/24
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;日志&#34;&gt;日志&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;使用可读的时间格式输出日志：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl logs -f fluentbit-gke-qq9w9  -c fluentbit --timestamps
2020-09-10T13:10:49.822321364Z Fluent Bit v1.3.11
2020-09-10T13:10:49.822373900Z Copyright (C) Treasure Data
2020-09-10T13:10:49.822379743Z
2020-09-10T13:10:49.822383264Z [2020/09/10 13:10:49] [ info] Configuration:
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;只输出尾部日志：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;kubectl logs -f fluentbit-gke-qq9w9  -c fluentbit --tail=10
[2020/09/10 13:10:49] [ info] ___________
[2020/09/10 13:10:49] [ info]  filters:
[2020/09/10 13:10:49] [ info]      parser.0
...
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;输出一个 Pod 中所有容器的日志：&lt;/p&gt;

&lt;p&gt;kubectl -n my-namespace logs -f my-pod &amp;ndash;all-containers&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使用标签选择器输出多个 Pod 的日志：&lt;/p&gt;

&lt;p&gt;kubectl -n my-namespace logs -f -l app=nginx&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;获取“前一个”容器的日志（例如崩溃的情况）：&lt;/p&gt;

&lt;p&gt;kubectl -n my-namespace logs my-pod &amp;ndash;previous&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;其它&#34;&gt;其它&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;把 Secret 复制到其它命名空间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;kubectl get secrets -o json --namespace namespace-old | \
  jq &#39;.items[].metadata.namespace = &amp;quot;namespace-new&amp;quot;&#39; | \
  kubectl create-f  -
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;下面两个命令可以生成一个用于测试的自签发证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj &amp;quot;/CN=grafana.mysite.ru/O=MyOrganization&amp;quot;
kubectl -n myapp create secret tls selfsecret --key tls.key --cert tls.crt
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;本文没什么结论，但是可以提供一个小列表，其中包含本文相关的有用链接。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Kubernetes 官方文档：&lt;code&gt;https://kubernetes.io/docs/reference/kubectl/cheatsheet/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Linux Academy 的入门参考：&lt;code&gt;https://linuxacademy.com/blog/containers/kubernetes-cheat-sheet/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Blue Matador 分类整理的命令列表：&lt;code&gt;https://www.bluematador.com/learn/kubectl-cheatsheet&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;另一个命令指南，部分内容和本文重复：&lt;code&gt;https://gist.github.com/pydevops/0efd399befd960b5eb18d40adb68ef83&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;kubectl 别名搜集：&lt;a href=&#34;https://github.com/ahmetb/kubectl-aliases&#34; target=&#34;_blank&#34;&gt;https://github.com/ahmetb/kubectl-aliases&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 无状态应用的一般特征</title>
      <link>/post/features-of-kubernetes-apps/</link>
      <pubDate>Mon, 07 Sep 2020 22:42:02 +0800</pubDate>
      <guid>/post/features-of-kubernetes-apps/</guid>
      <description>

&lt;p&gt;以 12 要素为代表的微服务标准，很好地给微服务的特征做出了指导。然而具体到以容器形式在 Kubernetes 上运行的无状态业务应用上，这个标准是有些高层的——它看重的是方法和架构。如果仅从外在视角来对一个“顺眼”的 Kubernetes 应用进行观察，这个应用应该有什么特征呢？&lt;/p&gt;

&lt;h2 id=&#34;依赖关系清晰&#34;&gt;依赖关系清晰&lt;/h2&gt;

&lt;p&gt;微服务应用通常会有各种外部依赖，例如数据库、缓存、队列等平台能力，或者业务上的依赖服务等，因此一个健康的微服务组合而成的应用，必须能处理好依赖关系。&lt;/p&gt;

&lt;p&gt;微服务的启动顺序不是固定的，并且存在独立更新、重启的可能。而很多应用仅在启动时进行连接，这就要求在 Kubernetes 上运行的应用，首先在启动时，不会因为暂时无法连接依赖服务直接崩溃；同时在运行期间，也有处理这种随时处理重连的能力。&lt;/p&gt;

&lt;h2 id=&#34;具备自检能力&#34;&gt;具备自检能力&lt;/h2&gt;

&lt;p&gt;存活检测关注的是进程是否活跃，是否应该重新启动；就绪检测代表的是服务能力，是否应该保存在 Service 的负载均衡池中。&lt;/p&gt;

&lt;p&gt;在没有设置就绪检测的情况下，Pod 一旦启动成功，K8s 就会把相关服务的请求发给该实例，如果这个实例启动较慢，就有可能对业务造成损失。同理，存活和就绪检测应该分别进行，例如业务阻塞时，暂时将实例摘除，但是无需重启，即可逐步恢复服务能力。&lt;/p&gt;

&lt;p&gt;联系到前面的依赖关系问题，在微服务环境中，一个服务的就绪检测应该仅仅关注本应用的情况，检测过程中不应包含对依赖服务的调用——否则所有依赖故障服务的其它服务的就绪检查失败，造成大面积故障。&lt;/p&gt;

&lt;h2 id=&#34;日志采集和处理&#34;&gt;日志采集和处理&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;应用不应继续把日志输出到本地文件，而应该输出到 &lt;code&gt;stdout&lt;/code&gt; 和 &lt;code&gt;stderr&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;集群应该针对容器的 &lt;code&gt;stdout&lt;/code&gt;、&lt;code&gt;stderr&lt;/code&gt; 提供统一的日志采集，建议使用 Daemonset 而非 Sidecar；&lt;/li&gt;
&lt;li&gt;进行日志采集的同时，集群应提供 ES、Loki 或其它类似机制来对日志进行处理，并且其处理和存储能力应该有初步预案；&lt;/li&gt;
&lt;li&gt;应用日志应提供分级开关，保证同一镜像在不同环境中可以输出不同数量和级别的日志信息。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;尽量优雅关停&#34;&gt;尽量优雅关停&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;容器命令入口应该有能力接收 &lt;code&gt;SIGTERM&lt;/code&gt;，并在需要的情况下传递给业务主进程；&lt;/li&gt;
&lt;li&gt;应用进程接收到 &lt;code&gt;SIGTERM&lt;/code&gt; 信号之后，不应立刻关停，而是处理好剩余的在途业务；&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;preStop&lt;/code&gt; 等 Pod 生命周期手段来完成特定任务；&lt;/li&gt;
&lt;li&gt;避免使用长连接，保持简单负载均衡的有效性。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;故障预防和应对&#34;&gt;故障预防和应对&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;避免运行单 Pod 的 Deployment；&lt;/li&gt;
&lt;li&gt;使用 Pod 软亲和避免同 Deployment 中的不同 Pod 分布在同一节点上；&lt;/li&gt;
&lt;li&gt;遭遇不可恢复的故障，应该允许应用崩溃，由 K8s 重新启动；&lt;/li&gt;
&lt;li&gt;定义 PDB（Pod disruption budgets），告知 K8s 为应用提供最低 Pod 数量保障。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;资源使用&#34;&gt;资源使用&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;必须定义 CPU 和内存的 Requests；&lt;/li&gt;
&lt;li&gt;必须定义内存的 Limits；&lt;/li&gt;
&lt;li&gt;同一集群中的不同微服务，如果有不同 QoS 要求，应该定义不同的 &lt;code&gt;qosClass&lt;/code&gt;，避免被无差别驱逐。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;安全相关&#34;&gt;安全相关&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;应清晰掌握并声明应用运行所需的 Linux Capabiltiy；&lt;/li&gt;
&lt;li&gt;避免使用 Root 身份运行容器；&lt;/li&gt;
&lt;li&gt;使用只读的 RootFS，所有写入需求应该使用存储卷来完成；&lt;/li&gt;
&lt;li&gt;避免特权逃逸。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 的授权和审计</title>
      <link>/post/rbac-psp-ops/</link>
      <pubDate>Sun, 24 May 2020 22:51:33 +0800</pubDate>
      <guid>/post/rbac-psp-ops/</guid>
      <description>

&lt;p&gt;Kubernetes 中的账号和认证，除了基础的双向证书认证之外，还有 OIDC 等方式的第三方集成能力，这里暂且不提。这里主要想谈谈授权和审计方面的内容。&lt;/p&gt;

&lt;p&gt;很多 Kubernetes 集群，都是一个 cluster-admin 走天下的，这和 Linux 里面只使用一个 root 账号一样，因此要完成授权和审计任务，首先需要创建的东西就是一个新用户。&lt;/p&gt;

&lt;p&gt;在使用本地证书进行用户管理的情况下，创建新用户通常有这样几种方法：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;创建一个新的 ServiceAccount，使用 SA 的 Token 进行认证。&lt;/li&gt;
&lt;li&gt;使用 Kubernetes 所使用的 CA，签发新的客户端证书。&lt;/li&gt;
&lt;li&gt;创建 CSR，提交到 Kubernetes 上，通过后，获取客户端证书。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;上面几个方法，完成后生成 kubeconfig 文件，并使用 RBAC &lt;strong&gt;为新用户进行最小化授权&lt;/strong&gt;，就可以用这些新用户的身份来完成“普通”用户的操作了。&lt;/p&gt;

&lt;h2 id=&#34;在-kubeadm-中启用审计&#34;&gt;在 Kubeadm 中启用审计&lt;/h2&gt;

&lt;p&gt;Kubeadm 的配置文件中加入如下内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiServer:
  extraArgs:
    audit-log-path: /var/log/k8s/audit.log
    audit-policy-file: /etc/kubernetes/audit.yaml
    audit-log-maxage: &amp;quot;1&amp;quot;
    audit-log-maxsize: &amp;quot;100&amp;quot;
    audit-log-maxbackup: &amp;quot;1&amp;quot;
  extraVolumes:
  - name: audit-config
    hostPath: /etc/k8s/audit.yaml
    mountPath: /etc/kubernetes/audit.yaml
    readOnly: true
    pathType: File
  - name: audit-log
    hostPath: /var/log/k8s
    mountPath: /var/log/k8s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个文件中指定了两个加载卷，分别用于存储配置文件和审计日志。API Server 会据此进行审计配置并输出日志。官方提供了审计策略的配置样例：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: audit.k8s.io/v1 # This is required.
kind: Policy
# Don&#39;t generate audit events for all requests in RequestReceived stage.
omitStages:
  - &amp;quot;RequestReceived&amp;quot;
rules:
  - level: None
    verbs: [&amp;quot;get&amp;quot;, &amp;quot;list&amp;quot;, &amp;quot;watch&amp;quot;]
  # Log pod changes at RequestResponse level
  - level: RequestResponse
    resources:
    - group: &amp;quot;&amp;quot;
      # Resource &amp;quot;pods&amp;quot; doesn&#39;t match requests to any subresource of pods,
      # which is consistent with the RBAC policy.
      resources: [&amp;quot;pods&amp;quot;]
  # Log &amp;quot;pods/log&amp;quot;, &amp;quot;pods/status&amp;quot; at Metadata level
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有了 RBAC + 审计，结合专人专用的操作账户，我们就能够对用户在集群上的操作有个初步的了解，例如如下记录：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;...
  &amp;quot;requestURI&amp;quot;: &amp;quot;/apis/apps/v1/namespaces/default/deployments&amp;quot;,
  &amp;quot;verb&amp;quot;: &amp;quot;create&amp;quot;,
  &amp;quot;user&amp;quot;: {
    &amp;quot;username&amp;quot;: &amp;quot;commonuser&amp;quot;,
    &amp;quot;groups&amp;quot;: [
      &amp;quot;dev&amp;quot;,
      &amp;quot;system:authenticated&amp;quot;
    ]
  }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;就代表用户 &lt;code&gt;commonuser&lt;/code&gt; 新建了一个 Deployment。审计信息中还包括了事件发生的时间、IP、当时的 RBAC 角色等。&lt;/p&gt;

&lt;p&gt;把审计日志汇总到 Elasticsearch 或者 Loki 之中，就能够获得集群范围内的所有我们关注的记录的操作了。&lt;/p&gt;

&lt;h2 id=&#34;为-kubeadm-集群启动-psp&#34;&gt;为 Kubeadm 集群启动 PSP&lt;/h2&gt;

&lt;p&gt;RBAC 决定一个用户能够操作什么资源（例如 Pod、Deployment、Service、SA 等），能够如何操作（例如创建、删除），而 PSP 则确定了 Pod 自身所能完成的任务：例如加载主机卷、使用 sysctl 等。&lt;/p&gt;

&lt;p&gt;启动 PSP 是个相对来说较危险的操作。首先要做的就是获取现在运行之中的应用所使用的策略，并将策略涉及的权限和当前使用的 ServiceAccount 对应起来，简单说来步骤如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;遍历运行中的 Pod，获取其中的特权情况，以及当前使用的 ServiceAccount。&lt;/li&gt;
&lt;li&gt;根据特权情况编写 &lt;code&gt;PodSecurityPolicy&lt;/code&gt; 策略。&lt;/li&gt;
&lt;li&gt;为策略设置 &lt;code&gt;Role&lt;/code&gt; 或者 &lt;code&gt;ClusterRole&lt;/code&gt;，绑定到 ServiceAccount。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Krew 有个 psp-advice 插件，能完成上面的前两个步骤——形成单一的 PSP 对象，相对来说有些粗放。例如使用这个插件在一个启动了 Calico 的 Kubeadm 集群上生成的 PSP（&lt;code&gt;kubectl advise-psp inspect&lt;/code&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  creationTimestamp: null
spec:
  allowedHostPaths:
  - pathPrefix: /lib/modules
    readOnly: true
  - pathPrefix: /var/lib/calico
    readOnly: true
  - pathPrefix: /opt/cni/bin
    readOnly: true
  - pathPrefix: /var/run/nodeagent
    readOnly: true
  - pathPrefix: /var/lib/cni/networks
    readOnly: true
  - pathPrefix: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/nodeagent~uds
    readOnly: true
  - pathPrefix: /run/xtables.lock
    readOnly: true
  - pathPrefix: /etc/cni/net.d
    readOnly: true
  - pathPrefix: /var/run/calico
    readOnly: true
  fsGroup:
    rule: RunAsAny
  hostNetwork: true
  hostPorts:
  hostPorts:
  - max: 0
    min: 0
  privileged: true
  runAsUser:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  volumes:
  - hostPath
  - secret
  - configMap
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们可以简单的为这个 PSP 生成一个 Role，并绑定到当前运行的 SA 上：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: kube-system-psp
  namespace: kube-system
rules:
- apiGroups:
  - extensions
  resourceNames:
  - kube-system
  resources:
  - podsecuritypolicies
  verbs:
  - use
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kube-system-psp
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kube-system-psp
subjects:
- kind: ServiceAccount
  name: default
  namespace: kube-system
- kind: ServiceAccount
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;完成所有步骤之后，在 API Server 的 &lt;code&gt;--enable-admission-plugins&lt;/code&gt; 参数里面加入 &lt;code&gt;PodSecurityPolicy&lt;/code&gt;，重新启动服务即可。&lt;/p&gt;

&lt;p&gt;接下来就可以使用 PSP 为各个不同的 SA、用户创建角色，确定各种情况下创建 Pod 的安全策略，例如绝大多数业务 Pod 是无需使用特权模式、HostNetwork 的。&lt;/p&gt;

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

&lt;p&gt;各司其职是个基础，在 RBAC/PSP 支持下，能够比较容易的针对用户、ServiceAccount、Namespace 进行细粒度的权限控制，例如对各种资源的操作权限、对 Pod 的日志、Exec 等操作、以及 Pod 自身的卷加载、特权申请等都可以进行控制，并且还可以通过审计功能进行事后追溯。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的 Pod 安全策略</title>
      <link>/post/psp-in-kubernetes/</link>
      <pubDate>Sun, 03 May 2020 10:29:06 +0800</pubDate>
      <guid>/post/psp-in-kubernetes/</guid>
      <description>

&lt;p&gt;很多人分不清 &lt;code&gt;SecurityContext&lt;/code&gt; 和 &lt;code&gt;PodSecurityPolicy&lt;/code&gt; 这两个关键字的差别，其实很简单：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SecurityContext 是 Pod 中的一个字段，而 PSP 是一个独立的资源类型。&lt;/li&gt;
&lt;li&gt;SecurityContext 是 Pod 自身对安全上下文的声明；而 PSP 则是强制实施的——不合规矩的 Pod 无法创建。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PSP 的用法和 RBAC 是紧密相关的，换句话说，应用 PSP 的基础要求是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;不同运维人员的操作账号需要互相隔离并进行单独授权。&lt;/li&gt;
&lt;li&gt;不同命名空间，不同 ServiceAccount 也同样要纳入管理流程。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PSP 环境下，运维人员或者新应用要接入集群，除了 RBAC 设置之外，还需要声明其工作范围所需的安全策略，并进行绑定，才能完成工作。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/policy/pod-security-policy/&#34; target=&#34;_blank&#34;&gt;PSP 的官方文档&lt;/a&gt;中提到，PSP 是通过 &lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#podsecuritypolicy&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;Admission Controller&lt;/code&gt;&lt;/a&gt; 启用的，并且注明了：启用 PSP 是一个有风险的工作，未经合理授权，可能导致 Pod 无法创建。&lt;/p&gt;

&lt;p&gt;开始之前，首先设置一个别名，在 &lt;code&gt;default&lt;/code&gt; 命名空间新建 ServiceAccount 来模拟一个有权创建 Pod 的用户：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ kubectl create sa common
serviceaccount/common created

$ kubectl create rolebinding common --clusterrole=edit --serviceaccount=default:common
rolebinding.rbac.authorization.k8s.io/common created

$ alias kube-common=&#39;kubectl --as=system:serviceaccount:default:common&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;第一个-psp&#34;&gt;第一个 PSP&lt;/h2&gt;

&lt;p&gt;我们首先创建一个不允许创建特权 Pod 的策略：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: noprivileged
spec:
  privileged: false
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  runAsUser:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  volumes:
  - &#39;*&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;保存为 &lt;code&gt;psp.noprivileged.yaml&lt;/code&gt; 并提交给集群。&lt;/p&gt;

&lt;p&gt;接下来创建两个 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: noprivileged
spec:
  containers:
  - name: pause
    image: k8s.gcr.io/pause
---
apiVersion: v1
kind: Pod
metadata:
  name: privileged
spec:
  containers:
  - name: pause
    image: k8s.gcr.io/pause
    securityContext:
      privileged: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用普通用户创建这个 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kube-common apply -f pod.yaml &amp;amp;&amp;amp; kube-common delete -f pod.yaml
pod/noprivileged created
pod/privileged created
pod &amp;quot;noprivileged&amp;quot; deleted
pod &amp;quot;privileged&amp;quot; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，在不允许创建特权容器的规则之中，我们的用户还是能够创建特权容器，这是因为还没启用 PSP，接下来在集群设置中启动 PSP，各种环境的启用方式不同，例如在 GKE 环境：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ gcloud beta container clusters update gcp-k8s --enable-pod-security-policy --zone=asia-east1-a
Updating gcp-vlab-k8s...done.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;删除重建 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kube-common apply -f pod.yaml &amp;amp;&amp;amp; kube-common delete -f pod.yaml
Error from server (Forbidden): error when creating &amp;quot;pod.yaml&amp;quot;: pods &amp;quot;noprivileged&amp;quot; is forbidden: unable to validate against any pod security policy: []
Error from server (Forbidden): error when creating &amp;quot;pod.yaml&amp;quot;: pods &amp;quot;privileged&amp;quot; is forbidden: unable to validate against any pod security policy: []
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，Pod 的新建请求被拒绝了——&lt;strong&gt;然而使用集群管理员身份还是能成功创建的&lt;/strong&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f pod.yaml &amp;amp;&amp;amp; kubectl delete -f pod.yaml
pod/noprivileged created
pod/privileged created
pod &amp;quot;noprivileged&amp;quot; deleted
pod &amp;quot;privileged&amp;quot; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;全员 admin 是万恶之源。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;用 RBAC 进行授权：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create role psp:noprivileged \
    --verb=use \
    --resource=podsecuritypolicy \
    --resource-name=noprivileged
role.rbac.authorization.k8s.io/psp:noprivileged created

$ kubectl create rolebinding common:psp:noprivileged \
    --role=psp:noprivileged \
    --serviceaccount=default:common
rolebinding.rbac.authorization.k8s.io/common:psp:noprivileged created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再试试普通用户的能力：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kube-common apply -f pod.yaml ; kube-common delete -f pod.yaml
pod/noprivileged created
Error from server (Forbidden): error when creating &amp;quot;pod.yaml&amp;quot;: pods &amp;quot;privileged&amp;quot; is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]
pod &amp;quot;noprivileged&amp;quot; deleted
Error from server (NotFound): error when deleting &amp;quot;pod.yaml&amp;quot;: pods &amp;quot;privileged&amp;quot; not found
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;非特权 Pod 才能够成功创建，这符合我们的预期。&lt;/p&gt;

&lt;h2 id=&#34;副作用&#34;&gt;副作用&lt;/h2&gt;

&lt;p&gt;Pod 成功创建了之后，顺理成章，做个 Deployment 看看：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Deployment
metadata:
  name: privileged
spec:
  replicas: 1  
  template:
    metadata:
      labels:
        app: pause
        version: v1
    spec:
      containers:
      - name: sleep
        image: k8s.gcr.io/pause
        securityContext:
          privileged: true
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: noprivileged
spec:
  replicas: 1
  template:
    serviceAccount: common
    metadata:
      labels:
        app: pause
        version: v1
    spec:
      containers:
      - name: sleep
        image: k8s.gcr.io/pause
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们会发现，Deployment 无法正常工作：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get pods
kuNo resources found in default namespace.
$ kubectl get deployment
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
noprivileged   0/1     0            0           15m
privileged     0/1     0            0           15m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看一下事件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get events | grep policy
8m38s       Warning   FailedCreate        replicaset/noprivileged-6f94f9c9b8                 Error creating: pods &amp;quot;noprivileged-6f94f9c9b8-&amp;quot; is forbidden: unable to validate against any pod security policy: []
8m38s       Warning   FailedCreate        replicaset/privileged-6d78d5458                    Error creating: pods &amp;quot;privileged-6d78d5458-&amp;quot; is forbidden: unable to validate against any pod security policy: []
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这次的 Pod 不是由我们授权的 &lt;code&gt;common&lt;/code&gt; 用户创建的，而是由 RS Controller 启动的，因此会失败，加入一个 Service Account：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
    spec:
      serviceAccount: common
      containers:
...
    spec:
      serviceAccount: common
      containers:
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交变更，会发现非特权 Pod 开始创建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
noprivileged-6cf595c5bd-rc8cx   1/1     Running   0          4s
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;系统-pod-怎么办&#34;&gt;系统 Pod 怎么办&lt;/h2&gt;

&lt;p&gt;这时候我想到个问题，其它 Pod 会不会受到影响？我删除了 &lt;code&gt;kube-system&lt;/code&gt; 下面的一个 &lt;code&gt;kube-proxy&lt;/code&gt; 的 Pod，发现这个 Pod 自动重建了，没有受到 PSP 的影响，查看一下 RBAC 相关配置，会发现 GCP 在更新集群的过程中已经为系统服务进行了预设：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get rolebinding
...
gce:podsecuritypolicy:kube-proxy                    80m
gce:podsecuritypolicy:metadata-agent                80m
gce:podsecuritypolicy:metadata-proxy                80m
gce:podsecuritypolicy:nodes                         80m
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;追查下去：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get rolebinding gce:podsecuritypolicy:metadata-proxy -o yaml
...
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: gce:podsecuritypolicy:privileged
subjects:
- kind: ServiceAccount
  name: metadata-proxy
  namespace: kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果追查其中涉及到的 ClusterRole，会发现它指向一个 PSP：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get clusterrole gce:podsecuritypolicy:privileged -o yaml
...
rules:
- apiGroups:
  - policy
  resourceNames:
  - gce.privileged
  resources:
  - podsecuritypolicies
  verbs:
  - use
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看看这个 PSP 的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get psp  gce.privileged -o yaml
...
    privileged: true
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;的确包含了特权 Pod 的内容。&lt;/p&gt;

&lt;p&gt;最后看看负责创建这个特权 Pod 的 Daemonset：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$  kubectl get daemonset  metadata-proxy-v0.1 -o yaml
...
      serviceAccount: metadata-proxy
      serviceAccountName: metadata-proxy
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;psp-的限制能力&#34;&gt;PSP 的限制能力&lt;/h2&gt;

&lt;p&gt;分为以下几个大方面：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;特权容器&lt;/li&gt;
&lt;li&gt;主机命名空间：例如 HostPID、HostNetwork 等。&lt;/li&gt;
&lt;li&gt;卷和文件系统：例如 PVC、configMap、emptyDir 等卷类型，以及 fsGroup、AllowedHostPaths 等加载能力。&lt;/li&gt;
&lt;li&gt;用户和组：运行身份&lt;/li&gt;
&lt;li&gt;提权：是否允许&lt;/li&gt;
&lt;li&gt;Capability 和 sysctl&lt;/li&gt;
&lt;li&gt;SeLinux、AppArmor 等。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;马后炮&#34;&gt;马后炮&lt;/h2&gt;

&lt;p&gt;kubectl 的 &lt;code&gt;advise-psp&lt;/code&gt; 插件，能够根据当前运行的 Pod，提取出所需的 PSP 信息。&lt;/p&gt;

&lt;h2 id=&#34;参考链接&#34;&gt;参考链接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://kubernetes.io/docs/concepts/policy/pod-security-policy/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>在 Kubernetes 上用 Krustlet 调度 WASM</title>
      <link>/post/wasm-node-on-k8s/</link>
      <pubDate>Tue, 14 Apr 2020 12:42:59 +0800</pubDate>
      <guid>/post/wasm-node-on-k8s/</guid>
      <description>

&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;

&lt;p&gt;坊间有两个传言：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 正在成长为一个统一调度器&lt;/li&gt;
&lt;li&gt;WASM 如果早点成气候，就没 Docker 什么事了&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/deislabs/krustlet&#34; target=&#34;_blank&#34;&gt;Krustlet&lt;/a&gt; 往前踏了一小步：他的官方描述是“Kubernetes Kubelet in Rust for running WASM”——使用 Rust 实现的 Kubelet，可以在 Kubernetes 中运行 WASM。&lt;/p&gt;

&lt;p&gt;Krustlet 是 Deis 实验室的产品，算是 Helm 的同门师兄弟。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;项目还非常初期，包括镜像拉取、Pod 生命周期等功能都没能完整实现，只能作为一个概念方面的尝试。&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;官方提供了 EKS、AKS 以及 Kind 的部署方案，这里我们使用 Kind 进行部署。使用如下配置文件定义一个三节点集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kind create cluster --config=config-3-node.yaml
Creating cluster &amp;quot;kind&amp;quot; ...
 ✓ Ensuring node image (kindest/node:v1.17.0) 🖼
 ✓ Preparing nodes 📦 📦 📦 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;集群启动之后，就可以安装 Krustlet 了。&lt;/p&gt;

&lt;p&gt;踩坑过程中，注意到 Krustlet 的几个依赖项目：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Openssl 1.1.x 支持，所以有些老系统可能需要升级；&lt;/li&gt;
&lt;li&gt;接入 Krustlet 时，需要满足接入新节点的 Kubernetes 权限要求；&lt;/li&gt;
&lt;li&gt;Krustlet 和 Kubelet 一样，也要使用 kubeconfig 文件进行认证。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在 &lt;a href=&#34;https://github.com/deislabs/krustlet/releases/&#34; target=&#34;_blank&#34;&gt;Release 页面&lt;/a&gt;可能找到压缩包下载，目前支持 Linux 和 MacOS 两个平台。&lt;/p&gt;

&lt;p&gt;解压之后，创建 &lt;code&gt;~/.krustlet/lib&lt;/code&gt; 目录，将压缩包中的 &lt;code&gt;*.so&lt;/code&gt; 和 &lt;code&gt;*.dylib&lt;/code&gt; 复制到 &lt;code&gt;~/.krustlet/lib&lt;/code&gt; 之中，可执行文件复制到 &lt;code&gt;/usr/local/bin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;接下来为 krustlet 准备用于加入 Kubernetes 的证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
mkdir -p ~/.krustlet/config
cd ~/.krustlet/config
openssl req -new -sha256 -newkey rsa:2048 -keyout krustlet.key -out krustlet.csr -days 365 -nodes -subj &amp;quot;/C=US/ST=./L=./O=./OU=./CN=krustlet&amp;quot;
cat &amp;lt;&amp;lt;EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
  name: krustlet
spec:
  request: $(cat krustlet.csr | base64 | tr -d &#39;\n&#39;)
  usages:
  - digital signature
  - key encipherment
  - server auth
EOF
kubectl certificate approve krustlet
kubectl get csr krustlet -o jsonpath=&#39;{.status.certificate}&#39; | base64 --decode &amp;gt; krustlet.crt
openssl pkcs12 -export -out certificate.pfx -inkey krustlet.key -in krustlet.crt -password &amp;quot;pass:password&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;启动&#34;&gt;启动&lt;/h2&gt;

&lt;p&gt;启动过程很简单：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ krustlet-wasi  --pfx-password password --node-ip 10.89.81.61
[2020-04-14T06:16:34Z ERROR kubelet::kubelet] Error handling event: error decoding response body: missing field `access_token` at line 1 column 501
[2020-04-14T06:18:04Z ERROR kubelet::kubelet] Error handling event: error decoding response body: missing field `detail` at line 1 column 119
[2020-04-14T06:28:37Z ERROR kubelet::kubelet] Error handling event: error decoding response body: missing field `detail` at line 1 column 119
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;其中的 &lt;code&gt;--node-ip&lt;/code&gt; 参数是可选的，在 Docker for OS X 中，需要用 &lt;code&gt;ifconfig en0&lt;/code&gt; 确定一下适配器 IP，如果是在物理机/虚拟机环境中，这个参数可以省略或者根据实际情况做出调整。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;是的你没看错，启动就出了点小问题。接下来看看节点情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get nodes
NAME                 STATUS   ROLES    AGE    VERSION
kind-control-plane   Ready    master   136m   v1.17.0
kind-worker          Ready    &amp;lt;none&amp;gt;   136m   v1.17.0
kind-worker2         Ready    &amp;lt;none&amp;gt;   136m   v1.17.0
kind-worker3         Ready    &amp;lt;none&amp;gt;   136m   v1.17.0
abcd-mb0       Ready    agent    111m   v1.17.0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看看节点的情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl describe nodes abcd-mb0
...
Taints:             krustlet/arch=wasm32-wasi:NoExecute
...
System Info:
  Machine ID:
  System UUID:
  Boot ID:
  Kernel Version:
  OS Image:
  Operating System:           linux
  Architecture:               wasm-wasi
  Container Runtime Version:  mvp
  Kubelet Version:            v1.17.0
  Kube-Proxy Version:         v1.17.0
...
  Operating System:           linux
  Architecture:               wasm-wasi
  Container Runtime Version:  mvp
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到有很多信息是缺失的，说明项目的确很早期。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Taints&lt;/code&gt; 字段的定义，要求特定 Pod 才能调度到该节点上。&lt;/p&gt;

&lt;p&gt;最后其架构显示的是 wasm-wasi。&lt;/p&gt;

&lt;h2 id=&#34;运行一个-pod&#34;&gt;运行一个 Pod&lt;/h2&gt;

&lt;p&gt;编写一个 YAML 文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: krustlet-tutorial
spec:
  containers:
    - name: krustlet-tutorial
      image: webassembly.azurecr.io/hello-world-wasi-rust:v0.1.0
      imagePullPolicy: Always
  tolerations:
    - key: &amp;quot;node.kubernetes.io/network-unavailable&amp;quot;
      operator: &amp;quot;Exists&amp;quot;
      effect: &amp;quot;NoSchedule&amp;quot;
    - key: &amp;quot;krustlet/arch&amp;quot;
      operator: &amp;quot;Equal&amp;quot;
      value: &amp;quot;wasm32-wasi&amp;quot;
      effect: &amp;quot;NoExecute&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;镜像名好像很普通，Pull 一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ docker pull webassembly.azurecr.io/hello-world-wasi-rust:v0.1.0
v0.1.0: Pulling from hello-world-wasi-rust
670adc713612: Pulling fs layer
invalid rootfs in image configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看来这个镜像并不普通。。后面会讲一下。&lt;/p&gt;

&lt;p&gt;Pod 定义中声明了 &lt;code&gt;tolerations&lt;/code&gt;，让该 Pod 可以在新节点上运行。提交 yaml 之后，可以看到 Pod 运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods -w
NAME                READY   STATUS    RESTARTS   AGE
krustlet-tutorial   0/1     Pending   0          6s
krustlet-tutorial   0/1     Running   0          8s
krustlet-tutorial   1/1     Running   0       8s
krustlet-tutorial   0/1     ExitCode:0   0          8s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到这个 Pod 飞快的完成了运行并成功退出，查看他的日志：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;hello from stdout!
hello from stderr!
Args are: []
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;前面使用 &lt;code&gt;docker pull&lt;/code&gt; 未能成功下载，这是因为这个镜像其实是一个封装为 OCI 的 WASM，可以使用 &lt;a href=&#34;https://github.com/engineerd/wasm-to-oci&#34; target=&#34;_blank&#34;&gt;was-to-oci&lt;/a&gt; 工具下载，并使用 &lt;a href=&#34;https://github.com/bytecodealliance/wasmtime&#34; target=&#34;_blank&#34;&gt;wasmtime&lt;/a&gt; 尝试运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ wasm-to-oci pull registry.microservice.rocks/module-wasm:v1
INFO[0007] Pulled: registry.microservice.rocks/module-wasm:v1
INFO[0007] Size: 1964621
INFO[0007] Digest: sha256:670adc7136128af3a2848d86c18013b1009e7dedb8a686ecacda175094aa083c
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;完成后可以看到当前目录出现了一个 &lt;code&gt;module.wasm&lt;/code&gt; 文件，运行一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ wasmtime module.wasm
hello from stdout!
hello from stderr!
Args are: [&amp;quot;module.wasm&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;反过来，也可以使用这个工具把 WASM 文件推送到镜像库中。例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$  wasm-to-oci push module.wasm registry.microservice.rocks/module-wasm:v1
INFO[0001] Pushed: registry.microservice.rocks/module-wasm:v1
INFO[0001] Size: 1964621
INFO[0001] Digest: sha256:54b09224f004231ffb37d14ac478a101d94c58aac93b8da7b67ed84147763d09
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;玩后感&#34;&gt;玩后感&lt;/h2&gt;

&lt;p&gt;目前 Krustlet 对 Docker Registry 的支持似乎是有问题的，因此无法运行我们自己用 &lt;code&gt;wasm-to-oci&lt;/code&gt; 推送到私库的 WASM。网络通信等内容也就无法测试了。好在目前版本只是 0.1.0，还有很多值得一等。&lt;/p&gt;
</description>
    </item>
    
    <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>给 Node Exporter 加上 Basic 认证</title>
      <link>/post/node_exp_basic_auth/</link>
      <pubDate>Thu, 09 Apr 2020 22:30:05 +0800</pubDate>
      <guid>/post/node_exp_basic_auth/</guid>
      <description>&lt;p&gt;前两天在成老师群里问了个无聊的问题——Node Exporter 输出的数据，是不是就应该匿名获取呢？本着 0 信任原则，缺省情况下使用 Host Network 的 Node Exporter 暴露的端口的确是令人稍有不安的，那么如何改善呢？&lt;/p&gt;

&lt;p&gt;Node Exporter 新版本中提供了一个 TLS 认证的实验性功能，恰好 Prometheus 也是支持双向 TLS 认证的。不过很多服务会通过 Endpoint 方式提供 Exporter 服务，用 Nginx Sidecar 会是个更加通用的方式。下面举个简单的例子，其他的 Exporter 也可以照猫画虎，并且 Nginx 很成熟，完全可以提供其他更丰富的认证能力。&lt;/p&gt;

&lt;p&gt;首先使用 Helm 安装 Prometheus：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;helm install stable/prometheus \
--generate-name \
--set alertmanager.enabled=false\
--set nodeExporter.hostNetwork=false \
--set pushgateway.enabled=false \
--set server.persistentVolume.enabled=false
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;启动之后，会生成一组 Prometheus 组件的资源对象， 要修改的包括几个项目：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;生成 Basic 认证所需的文件&lt;/li&gt;
&lt;li&gt;为 Nginx 编写反向代理配置&lt;/li&gt;
&lt;li&gt;以 Sidecar 的形式把 Nginx 加入 Node Exporter 的 Pod 中&lt;/li&gt;
&lt;li&gt;变更 Node Exporter 的抓取配置&lt;/li&gt;
&lt;li&gt;变更 Prometheus 的采集参数&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;可以使用 htpasswd 工具生成密码文件，例如：&lt;code&gt;htpasswd -c -m passwd.dat admin&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;接下来编写一个配置文件片段：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;server {
    listen  9101;
    server_name localhost;
    auth_basic   &amp;quot;login&amp;quot;;
    auth_basic_user_file /etc/nginx/conf.d/passwd.dat;

    location / {
        root   /usr/share/nginx/html;
        proxy_pass http://127.0.0.1:9100;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里使用一个非常简单的配置，引用前面生成的密码文件进行验证，并且对来自 9091 端口的请求，转发到同一个 Pod 中 9100 端口的 Node Exporter 上。&lt;/p&gt;

&lt;p&gt;用前面的两个文件生成 Configmap 供容器引用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;kubectl create configmap nginx-config \
--from-file=proxy.conf --from-file=passwd.dat
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;生成 Nginx 配置之后，就需要把 Nginx 加入 NodeExporter 了，可以使用 kubectl edit 在线编辑，或者导出 YAML，加入如下内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;    spec:
      containers:
        ...
        - image: nginx:stable-alpine
          ports:
          - containerPort: 9101
            name: proxy
            protocol: TCP
          name: nginx
          volumeMounts:
            - mountPath: /etc/nginx/conf.d
              name: nginx-config
              readOnly: true
      ...
      volumes:
        ...
        - name: nginx-config
          configMap:
            name: nginx-config
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为端口发生了变化，所以还需要修改 Service 的抓取标签，注解中加入：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  annotations:
    prometheus.io/port: &amp;quot;9101&amp;quot;
    prometheus.io/scrape: &amp;quot;true&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后修改 Prometheus 的配置，在 &lt;code&gt;kubernetes-service-endpoints&lt;/code&gt; 加入如下内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;    - job_name: kubernetes-service-endpoints
      basic_auth:
        username: admin
        password: password
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;重启 Prometheus，之后，可以看到工作还是继续进行，但是使用 CURL 访问会得到 401：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ curl http://192.168.14.252:9101/metrics
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;401 Authorization Required&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;center&amp;gt;&amp;lt;h1&amp;gt;401 Authorization Required&amp;lt;/h1&amp;gt;&amp;lt;/center&amp;gt;
&amp;lt;hr&amp;gt;&amp;lt;center&amp;gt;nginx/1.16.1&amp;lt;/center&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;以此类推，如果在 Nginx 中引入 TLS 双向认证，还可以使用 CA 的方式对认证过程进行进一步的集中管理，让更多的 Exporter 进入管理范围。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 1.18 中 Ingress 的增强</title>
      <link>/post/ingress-in-1.18/</link>
      <pubDate>Sun, 05 Apr 2020 17:41:31 +0800</pubDate>
      <guid>/post/ingress-in-1.18/</guid>
      <description>

&lt;p&gt;作者：&lt;a href=&#34;https://www.linkedin.com/in/robertjscott1&#34; target=&#34;_blank&#34;&gt;Rob Scott (Google)&lt;/a&gt; &lt;a href=&#34;https://www.linkedin.com/in/cmluciano&#34; target=&#34;_blank&#34;&gt;Christopher M Luciano (IBM)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/&#34; target=&#34;_blank&#34;&gt;Improvements to the Ingress API in Kubernetes 1.18&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 的 Ingress API 是大量 Ingress 控制器的基础，它们通过这一 API，用方便强大的方式为 Kubernetes 提供入站流量的支持。在 Kubernetes 1.18 中，这个 API 有了三个显著的变化：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;新的 &lt;code&gt;pathType&lt;/code&gt; 字段可以用来匹配 Ingress 路径。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IngressClass&lt;/code&gt; 资源能够指定控制器实现 Ingress 的方法。&lt;/li&gt;
&lt;li&gt;主机名中可以使用通配符。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;使用-path-type-改进路径匹配&#34;&gt;使用 Path Type 改进路径匹配&lt;/h2&gt;

&lt;p&gt;Path Type 的新概念让用户可以指定路径的匹配方式，目前有三种：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ImplementationSpecific&lt;/code&gt;（缺省）：这种匹配方式的行为取决于 &lt;code&gt;IngressClass&lt;/code&gt; 控制器的实现。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Extract&lt;/code&gt;：以区分大小写的方式精确匹配整个 URL 路径。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Prefix&lt;/code&gt;：区分大消息，根据以 &lt;code&gt;/&lt;/code&gt; 分割的 URL 元素进行前缀匹配。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;使用-ingress-class-扩展配置&#34;&gt;使用 Ingress Class 扩展配置&lt;/h2&gt;

&lt;p&gt;Ingress 资源的设计初衷就是易用性，尝试使用简单的字段为所有应用提供支持。随着应用场景的不断增加，为了适应更广泛的需求，越来越多的 Ingress 控制器要靠大量的自定义注解来完成更复杂的配置。&lt;code&gt;IngressClass&lt;/code&gt; 资源提供了一种替换部分注解的思路。&lt;/p&gt;

&lt;p&gt;每个 &lt;code&gt;IngressClass&lt;/code&gt; 中都指明了用于实现 Ingress 的控制器类型，并且可以引用自定义资源来使用更多参数。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
  name: external-lb
spec:
  controller: example.com/ingress-controller
  parameters:
    apiGroup: k8s.example.com/v1alpha
    kind: IngressParameters
    name: external-lb
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;指定-ingess-的-class&#34;&gt;指定 Ingess 的 Class&lt;/h3&gt;

&lt;p&gt;Ingress 规范中加入了 &lt;code&gt;ingressClassName&lt;/code&gt; 字段，用来指定实现这个 Ingress 资源的的 &lt;code&gt;IngressClass&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&#34;淘汰-ingress-注解&#34;&gt;淘汰 Ingress 注解&lt;/h3&gt;

&lt;p&gt;在 1.18 加入 &lt;code&gt;IngressClass&lt;/code&gt; 之前，需要在 Ingess 资源中使用 &lt;code&gt;kubernetes.io/ingress.class&lt;/code&gt; 注解来指定 Ingress 控制器。在没有官方定义的情况下，这个注解被大量的 Ingress 控制器所支持。现在是时候淘汰他了。&lt;/p&gt;

&lt;h3 id=&#34;设置缺省的-ingressclass&#34;&gt;设置缺省的 IngressClass&lt;/h3&gt;

&lt;p&gt;可以使用 &lt;code&gt;ingressclass.kubernetes.io/is-default-class&lt;/code&gt; 注解，将其设置为 &lt;code&gt;True&lt;/code&gt;，就代表所在的 &lt;code&gt;IngressClass&lt;/code&gt; 为缺省控制器。没有显示指定 &lt;code&gt;IngressClassName&lt;/code&gt; 的新的 Ingress 资源都会使用该控制器。&lt;/p&gt;

&lt;h2 id=&#34;主机名通配符&#34;&gt;主机名通配符&lt;/h2&gt;

&lt;p&gt;很多 Ingress 控制器都支持通配符，例如 &lt;code&gt;*.foo.com&lt;/code&gt; 可以匹配 &lt;code&gt;app1.foo.com&lt;/code&gt;，但是直到目前为止，规范还是假设使用完全匹配的 FQDN。主机名现在也可以使用通配符了。&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Host Header&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;code&gt;*.foo.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*.foo.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根据后缀匹配&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*.foo.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*.foo.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不匹配，通配符只能对应一个 DNS 项&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*.foo.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;foo.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不匹配，通配符只能对应一个 DNS 项&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

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

&lt;p&gt;新的 Ingress 功能扩展了配置能力，下面是一个例子，其中用到了上面提到的三个新特性：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: example-ingress
spec:
  ingressClassName: external-lb
  rules:
  - host: *.example.com
    http:
      paths:
      - path: /example
        pathType: Prefix
        backend:
          serviceName: example-service
          servicePort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;ingress-控制器支持&#34;&gt;Ingress 控制器支持&lt;/h3&gt;

&lt;p&gt;这个功能是 Kubernetes 1.18 中新增的，因此各种控制器都需要一段时间才能提供支持。请关注相关产品的官方文档。&lt;/p&gt;

&lt;h2 id=&#34;ingress-的未来&#34;&gt;Ingress 的未来&lt;/h2&gt;

&lt;p&gt;Ingress API 将在 1.19 进入稳定阶段。它会持续使用简单的方式为 Kubernetes 入站流量提供支持。这个 API 的设计重心就在于轻量和简单，但是更好的配置能力和更广泛的案例支持也是一个持续的努力方向。&lt;/p&gt;

&lt;p&gt;目前还在开发一组高配置能力的 API。被称为 &lt;code&gt;Service API&lt;/code&gt; 的新 API 会提供一种 Ingress 的替代方案。它的存在目的不是替代 Ingress，而是提供一种更具配置能力的新方案。请查看 Github 上的 &lt;a href=&#34;http://github.com/kubernetes-sigs/service-apis&#34; target=&#34;_blank&#34;&gt;Service API&lt;/a&gt; 项目。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>如何编写一个支持 Krew 的 kubectl 插件</title>
      <link>/post/how-to-krew/</link>
      <pubDate>Thu, 02 Apr 2020 10:13:24 +0800</pubDate>
      <guid>/post/how-to-krew/</guid>
      <description>

&lt;h2 id=&#34;krew-简介&#34;&gt;krew 简介&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/kubernetes-sigs/krew&#34; target=&#34;_blank&#34;&gt;Krew&lt;/a&gt; 是一个用来管理 Kubectl 插件的工具，名字大概来自于 OS X 下著名的软件包管理器 &lt;code&gt;Homebrew&lt;/code&gt;，使用 Krew 能够方便的查找、安装和使用 Kubectl 插件，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl krew search
NAME                            DESCRIPTION                                         INSTALLED
access-matrix                   Show an RBAC access matrix for server resources     no
advise-psp                      Suggests PodSecurityPolicies for cluster.           no
...

$ kubectl krew install tree
Updated the local copy of plugin index.
Installing plugin: tree
...

$  kubectl tree deployment coredns -nkube-system
NAMESPACE    NAME                                READY  REASON  AGE
kube-system  Deployment/coredns                  -              140d
kube-system  └─ReplicaSet/coredns-76d9d9bcc7   -              140d
kube-system    ├─Pod/coredns-76d9d9bcc7-m6d4c  True           4d10h
kube-system    └─Pod/coredns-76d9d9bcc7-zvf9c  True           4d10h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;很方便的几个步骤，就可以查询、安装和使用新插件了。&lt;/p&gt;

&lt;p&gt;Krew 除了落在客户端的可执行文件之外，和其它软件包管理系统一样，也同样需要有一个索引系统，并根据索引进行软件查询和下载，下载之后的软件保存在本地，供 kubectl 调用。&lt;/p&gt;

&lt;h3 id=&#34;索引&#34;&gt;索引&lt;/h3&gt;

&lt;p&gt;Krew 的索引保存在一个名为 &lt;a href=&#34;https://github.com/kubernetes-sigs/krew-index&#34; target=&#34;_blank&#34;&gt;krew-index&lt;/a&gt; 的代码库中。其中的 &lt;code&gt;plugins&lt;/code&gt; 目录保存了一组 yaml 文件，就是插件的目录。&lt;/p&gt;

&lt;h3 id=&#34;yaml-清单&#34;&gt;YAML 清单&lt;/h3&gt;

&lt;p&gt;随意打开一个清单文件，可以看到这样的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: krew.googlecontainertools.github.com/v1alpha2
kind: Plugin
metadata:
  name: access-matrix
spec:
  version: v0.4.4
  platforms:
  - bin: access-matrix
    uri: https://github.com/corneliusweig/rakkess/releases/download/v0.4.4/access-matrix-amd64-linux.tar.gz
    sha256: 53b1ee5865d11360cea3e59b91cdc6707ee30845567e63657782ee11815f1de4
    files:
      - from: ./LICENSE
        to: .
      - from: ./access-matrix-amd64-linux
        to: access-matrix
    selector:
      matchLabels:
        os: linux
        arch: amd64
  shortDescription: Show an RBAC access matrix for server resources
  homepage: https://github.com/corneliusweig/rakkess
  caveats: |
      Usage:
        kubectl access-matrix
  description: ..
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中 &lt;code&gt;apiVersion&lt;/code&gt; 和 &lt;code&gt;kind&lt;/code&gt; 是固定内容。&lt;code&gt;platforms&lt;/code&gt; 是一个数组，指定不同平台下的不同用法。下一级的 &lt;code&gt;bin&lt;/code&gt; 表明了执行命令；&lt;code&gt;uri&lt;/code&gt; 和 &lt;code&gt;sha256&lt;/code&gt; 分别指的是下载位置以及压缩包的校验码；接下来的 &lt;code&gt;files&lt;/code&gt; 是一个拷贝命令——从解压后的文件夹中拷贝文件；最后的 &lt;code&gt;selector&lt;/code&gt; 则是针对不同平台的选择标准。&lt;/p&gt;

&lt;p&gt;所以要编写一个能够通过 Krew 进行管理的 kubectl 插件，需要以下几个步骤：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;编写插件代码&lt;/li&gt;
&lt;li&gt;制作清单和调试&lt;/li&gt;
&lt;li&gt;上传到 krew-index&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下面用一个实际的例子来说明一下这个过程。&lt;/p&gt;

&lt;h2 id=&#34;编写插件代码&#34;&gt;编写插件代码&lt;/h2&gt;

&lt;p&gt;插件代码本身的编写非常简单和随意，可以用你喜欢的任何语言，例如 golang、python 或者 shell。只有一个推荐的命名规则：&lt;code&gt;kubectl-rm&lt;/code&gt;，在 kubectl 中调用时就可以使用 &lt;code&gt;kubectl rm&lt;/code&gt; 了。例如我要编写一个对输出 JSON 进行过滤的插件，代码如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh

METADATA=${JSON_METADATA-&amp;quot;.metadata.resourceVersion, .metadata.selfLink, .metadata.managedFields, .metadata.generation, .metadata.uid, .metadata.creationTimestamp&amp;quot;}
STATUS=${JSON_STATUS-&amp;quot;.status&amp;quot;}
ANNOTATION=${JSON_ANNOTATION-&amp;quot;.metadata.annotations.\&amp;quot;kubectl.kubernetes.io/last-applied-configuration\&amp;quot;, .metadata.annotations.\&amp;quot;deployment.kubernetes.io/revision\&amp;quot;&amp;quot;}
SPEC=${JSON_SPEC-&amp;quot;.spec.template.metadata.creationTimestamp, .spec.revisionHistoryLimit, .spec.templateGeneration&amp;quot;}

if ! [ -x &amp;quot;$(command -v jq)&amp;quot; ]; then
  echo &#39;Error: jq is not installed.&#39; &amp;gt;&amp;amp;2
  exit 1
fi

if [ $# -lt 2 ]
  then
    echo &amp;quot;Usage: $0 [workload-type] [object-name] [other parameters for kubectl]&amp;quot;
    echo &amp;quot;Workload types: &#39;deployment&#39;, &#39;daemonset&#39;, &#39;configmap&#39;, &#39;statefulset&#39;, &#39;secret&#39;&amp;quot;
    echo &amp;quot;Example: $0 deploy coredns -n kube-system&amp;quot;
    exit 1
fi

TYPE=$1
NAME=$2
OTHER=$*

kubectl get ${OTHER} -ojson | jq -S &amp;quot;del(${METADATA}, ${STATUS}, ${ANNOTATION}, ${SPEC})&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;想法很简单，获取运行中的对象描述，使用 JQ 对数据进行清理和排序，输出一个相对标准的结果，便于不同环境间的比较和部署的导出。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;虽然最后是通过 &lt;code&gt;kubectl std-json&lt;/code&gt; 的方式调用，这里的 &lt;code&gt;$0&lt;/code&gt; 指的仍然是脚本自身。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;制作清单和测试&#34;&gt;制作清单和测试&lt;/h2&gt;

&lt;p&gt;照猫画虎，按照上面的 YAML 代码，编写自己的清单。&lt;/p&gt;

&lt;p&gt;清单要求，需要打一个压缩包便于下载，我们把可执行文件和 LICENSE 文件放置到单独的目录 &lt;code&gt;kubectl-std-json-v0.1.0&lt;/code&gt; 中，压缩生成一个 &lt;code&gt;.tar.gz&lt;/code&gt; 文件，部分清单如下&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;    uri: https://github.com/fleeto/kubectl-std-json/releases/download/v0.1.0/kubectl-std-json-v0.1.0.tar.gz
    sha256: e1ad2398eaed5442042da134fb046fa8276042dd4122da4d872a8e91aeb2a339
    bin: kubectl-std-json
    files:
    - from: kubectl-std-json-*/kubectl-std-json
      to: .
    - from: kubectl-std-json-*/LICENSE
      to: .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;平台选择方面，我们只支持 OSX 和 Linux，因此只要一个平台元素即可。&lt;/p&gt;

&lt;p&gt;压缩包的校验码可以使用 &lt;code&gt;shasum -a 256&lt;/code&gt; 命令生成。&lt;/p&gt;

&lt;p&gt;上传压缩包之后，可以使用 &lt;code&gt;kubectl krew install --manifest&lt;/code&gt; 命令来测试安装。如果一切顺利，在本地就可以使用了。&lt;/p&gt;

&lt;h2 id=&#34;krew-index&#34;&gt;krew-index&lt;/h2&gt;

&lt;p&gt;接下来的操作很常规：fork krew-index，把你的清单写入 &lt;code&gt;plugins&lt;/code&gt; 目录，提交 PR 即可。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Krew：&lt;code&gt;https://github.com/kubernetes-sigs/krew&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Krew index：&lt;code&gt;https://github.com/kubernetes-sigs/krew-index&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 集群规模杂谈</title>
      <link>/post/someting-about-cluster-size/</link>
      <pubDate>Sun, 22 Mar 2020 08:38:38 +0800</pubDate>
      <guid>/post/someting-about-cluster-size/</guid>
      <description>

&lt;h2 id=&#34;节点数量&#34;&gt;节点数量&lt;/h2&gt;

&lt;p&gt;早在 Kubernetes 1.2 时候，就已经宣布达到 1000 节点的规模了，在 1.6 版本更达到了 5000 节点的规模。各大厂也都有了各自的超大规模单一集群。然而普罗大众的情况是如何呢？ Sysdig 在 2019 年度容器应用报告中得到的结果是，大于 50 节点规模的集群不足 10%，另外一个佐证是 Mohamed Ahmed 的一篇调查报告中也提供了类似的数据。这种情况的一种解释是，目前的应用阶段还比较早期，处于试探期间；然而从一个侧面来说，Sysdig 的调研对象针对的是生产应用，也就是说处于生产应用状态下的集群，绝大多数都是这种小规模集群。根据对 CNCF Landscape 中 Distribution 分类的产品的抽查，也可以看到随处可见的 Kubernetes As Service 类似功能的实现，这也证实了小集群协作方案的落地趋势。相对于少量大集群，多个小集群的差异在于：&lt;/p&gt;

&lt;h3 id=&#34;隔离程度高&#34;&gt;隔离程度高&lt;/h3&gt;

&lt;p&gt;虽然现在存在不少沙箱容器实现，然而最易用的、生态最为成熟的方案还是 Docker为代表的传统容器方案，传统容器方案所缺失的隔离能力，通过多租户多集群方式是一个非常自然的思路。&lt;/p&gt;

&lt;h3 id=&#34;实现难度低&#34;&gt;实现难度低&lt;/h3&gt;

&lt;p&gt;国内几个大厂都有自己的大规模 Kubernetes 集群实现方式，然而通常需要对基础组件大动干戈，甚至不惜使用无法回流社区的孤岛版本，虽然部分大企业的研究院等相关部门已经具备了非常强的研发实力，然而对于通常的 To B 场景来说，这并不是一个合适的选择。&lt;/p&gt;

&lt;h3 id=&#34;运管成本高&#34;&gt;运管成本高&lt;/h3&gt;

&lt;p&gt;多个集群很明显会需要更多的运维和管理人力的投入。&lt;/p&gt;

&lt;h3 id=&#34;资源利用率低&#34;&gt;资源利用率低&lt;/h3&gt;

&lt;p&gt;多个集群都会有自己的 Master 组件、ETCD 集群、网络组件等，这些都会抢占更多原本属于工作负载的系统资源，客观上降低了资源的总体利用率。&lt;/p&gt;

&lt;h2 id=&#34;节点尺寸&#34;&gt;节点尺寸&lt;/h2&gt;

&lt;p&gt;目前很多 Kubernetes 系统都会使用虚拟机来做为节点。那么虚拟机的资源是多分还是少分呢？下表是一个简单的对比：&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;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;多&lt;/td&gt;
&lt;td&gt;同样的资源总量情况下，相对来说小资源节点会得到更多的数量。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;运维成本&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;通常情况下，节点的运维成本是和节点数量正相关的。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;容错能力&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;较大的节点上通常会集中较多的应用，因此在节点出现故障时，可能会带来更大的损失。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;资源粒度&lt;/td&gt;
&lt;td&gt;大&lt;/td&gt;
&lt;td&gt;小&lt;/td&gt;
&lt;td&gt;单节点资源较大，因此其资源粒度也较大。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;应用副本数&lt;/td&gt;
&lt;td&gt;少&lt;/td&gt;
&lt;td&gt;多&lt;/td&gt;
&lt;td&gt;同一应用的多个副本，如果调度到同一个节点上的话，对于提高其负载能力和健壮性来说并无裨益。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;副本规模&lt;/td&gt;
&lt;td&gt;大&lt;/td&gt;
&lt;td&gt;小&lt;/td&gt;
&lt;td&gt;毫无疑问，具备更多资源的大节点，能够运行更大资源需求范围的容器应用。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;系统开销&lt;/td&gt;
&lt;td&gt;少&lt;/td&gt;
&lt;td&gt;多&lt;/td&gt;
&lt;td&gt;每个虚拟机都会有自己的操作系统、网络等基础开销，因此相对于少量大节点来说，大量的小节点会消耗更多的资源。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;虚拟机分配难度&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;过大的节点资源需求，如果采用虚拟机分配，就需要有更大规模的物理机提供支持。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;除了这些原则性的条目之外，更重要的决策依据就是运行在集群上的应用需求。例如某租户的集群需要支撑 20 个应用，共 300 个 Pod，按照常见的每节点 30-50 Pod 的分布，就需要 6-10 个运算节点（Node）。以 10 节点算，加入系统保留、冗余等计算，可能需要 10 * 120G 的虚拟机实例；然而考虑到故障情况——一个节点的故障，最好的结果也是短期内降低 10% 的算力。如果扩张到 40 个 32G 的虚拟机节点，会大幅降低单节点故障的影响——当然也会提高网络的复杂性和效率要求。&lt;/p&gt;

&lt;h2 id=&#34;应用资源&#34;&gt;应用资源&lt;/h2&gt;

&lt;p&gt;Java 应用是特别常见的迁移案例，除掉微服务化、网格、分布式等改造要求之外，资源的申请和限制是一个必须要面对的门槛。requests 是个用于调度的定义，Kubernetes 根据这个要求来选择能够满足要求的节点来分配应用；而 limits 则会用于触发 OOM。&lt;/p&gt;

&lt;p&gt;众所周知的是，Java 的早期版本是无法识别容器内的内存限制的，因此如果没有限制堆内存上限，又开启了 limits，就会被 Kubernetes 杀掉。因此针对容器中运行的情况，需要进行一些启动参数的设置。&lt;/p&gt;

&lt;p&gt;如果允许更新到新版本的 JVM，可以使用新引入的 UseCGroupMemoryLimitForHeap、MaxRAMFraction 参数，让 JVM 直接继承容器的定义。&lt;/p&gt;

&lt;p&gt;如果无法直接升级，那么就有必要设置 xmx 和 xms 参数了，这里有几个小建议：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;xmx 和 xms，request 和 limits 建议设成一致，能省掉很多麻烦。&lt;/li&gt;
&lt;li&gt;tmpfs、filemapping 等都是可能的内存大户。&lt;/li&gt;
&lt;li&gt;JVM 并不是唯一的内存消耗者，一般建议 Limit 大于 XMX 25% 以上。&lt;/li&gt;
&lt;li&gt;/sys/fs/cgroup/memory/memory.stat 是你的好朋友。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;kubernetes-中的-cpu-和内存&#34;&gt;Kubernetes 中的 CPU 和内存&lt;/h2&gt;

&lt;p&gt;Kubernetes 集群中的资源，主要关注的是 CPU 和内存两种。Pod 的定义中会定义对资源需求的声明，声明方式分为 Request 和 Limit。&lt;/p&gt;

&lt;p&gt;Request 是一个调度参数，可以理解为基本需求：一个 Pod 中的所有容器的 Request 之和，就是 Pod 对资源的最小需求，调度器根据这个最小需求来选择具备条件的节点，在其上运行被调度的 Pod。&lt;/p&gt;

&lt;p&gt;Limit 是一个安全参数，它的值一定大于 Request，顾名思义，它声明的是上限：&lt;/p&gt;

&lt;p&gt;CPU是弹性资源，如果容器使用CPU达到Limit，就无法进一步提高运算能力，可能会导致运算速度无法满足需求。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory 是非弹性资源，如果容器使用 Memory 达到 Limit，就会触发 cgroup 的 OOM 事件，导致容器被杀死。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;综上所述，Memory超限会对业务产生更大伤害，那么是不是不设限会更安全？答案很显然是否定的：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;不设置 Limit，一旦引发系统 OOM 或者驱逐事件，宏观来看，都会导致一个不可预知的结果。&lt;/li&gt;
&lt;li&gt;不设置 Request，Kubernetes 调度器会失去重要的调度标准，会影响负载分布的准确性。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一般来说如果 Limit 大于 Request（称为 Burstable），Kubernetes 会根据 Request 将 Pod 调度到满足 Request 要求的节点上去，然而一旦内存消耗从 Request 向着 Limit 增长的过程中出现了节点内存不足的情况，仍然会引发驱逐问题，因此对于保障级别高的业务，我们强烈建议将 Limit 设置为和 Request 相等。&lt;/p&gt;

&lt;h2 id=&#34;副本和节点数量&#34;&gt;副本和节点数量&lt;/h2&gt;

&lt;p&gt;目前 Kubernetes 的主流网络模型是基于 iptables 的，很显然 Service、Endpoint 和 Pod 并非越多越好。&lt;/p&gt;

&lt;p&gt;而对于应用来说更多的副本数往往意味着更好的容错能力——同样损失一个副本，越多总数意味着业务损失越小。&lt;/p&gt;

&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;https://dig.sysdig.com/c/pf-2019-container-usage-report?x=u_WFRi&amp;amp;mkt_tok=eyJpIjoiWW1GbVptUmtOakk1T1RVNCIsInQiOiJCUitxTXpSYUpXbVJOUDBUK09sbDh4aDVDNkZURHFXK0UwdUNEbkp6UG43XC9VamJIbm9obzJ6MDdcL3EwYXRHS0dTMVdrQXlJaEZDUFd5WnE0WUpXa1ZNVHZyRFkrYjlTNmhwb3d4cFk0alBSOHBqY09mY0pkaDV1VkZCeCtOaHpnIn0%3D&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>简介：CIS Kubernetes 安全基准指南</title>
      <link>/post/cis-benchmark-brief/</link>
      <pubDate>Sat, 22 Feb 2020 21:24:06 +0800</pubDate>
      <guid>/post/cis-benchmark-brief/</guid>
      <description>

&lt;p&gt;在使用 &lt;a href=&#34;https://github.com/aquasecurity/kube-bench&#34; target=&#34;_blank&#34;&gt;Kube Bench&lt;/a&gt; 的过程中注意到，其指导依据来自于 &lt;a href=&#34;https://www.cisecurity.org/benchmark/kubernetes/&#34; target=&#34;_blank&#34;&gt;CIS Benchmark&lt;/a&gt;，于是顺藤摸瓜，下载了 CIS Kubernetews Be nchmark 的 PDF 版本，全文有两百多页，阅读量还蛮大的，因此对其进行整理，便于大家参考使用。&lt;/p&gt;

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;CIS 的指导原则里把建议行为分成了两级：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一级：使用该建议不会造成负面影响。&lt;/li&gt;
&lt;li&gt;二级：仅建议在非常强调安全性的系统中使用，可能对系统有副作用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外还将具体的检测结果分为计分和不计分两种结果。&lt;/p&gt;

&lt;p&gt;以上两个维度可以用来对系统进行现状评估，也有助于读者选择性地采纳加固措施。&lt;/p&gt;

&lt;p&gt;整个指南分为五个部分：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;控制平面组件&lt;/li&gt;
&lt;li&gt;etcd&lt;/li&gt;
&lt;li&gt;控制平面配置&lt;/li&gt;
&lt;li&gt;节点配置&lt;/li&gt;
&lt;li&gt;策略&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;检查项概要&#34;&gt;检查项概要&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;所有运行参数文件、kubeconfig 文件以及证书，权限至少应为 &lt;code&gt;644&lt;/code&gt; 并且属于 &lt;code&gt;root:root&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;API Server&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;南向和北向通信

&lt;ul&gt;
&lt;li&gt;关闭匿名访问&lt;/li&gt;
&lt;li&gt;禁止明文通信&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;认证

&lt;ul&gt;
&lt;li&gt;启用 Node,RBAC 认证&lt;/li&gt;
&lt;li&gt;禁用 Token 和 Basic 认证&lt;/li&gt;
&lt;li&gt;禁用 Alwaysallow&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Admission Control

&lt;ul&gt;
&lt;li&gt;禁用：AlwaysAdmit&lt;/li&gt;
&lt;li&gt;启用：AlwaysPullImages、AlwaysAdmit、EventRateLimit、ServiceAccount、NamespaceLifecycle、PodSecurityPolicy、NodeRestriction&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;关闭 &lt;code&gt;profiling&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;启用审计日志&lt;/li&gt;
&lt;li&gt;启用请求超时&lt;/li&gt;
&lt;li&gt;启用 &lt;code&gt;--service-account-lookup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--tls-cipher-suites&lt;/code&gt; 仅使用新的、强加密算法&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;oidc&lt;/code&gt; 等模式来代替客户端证书认证。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Controller Manager&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;关闭 &lt;code&gt;profiling&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;开启 &lt;code&gt;--use-service-account-credentials&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;绑定 &lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;启用 &lt;code&gt;--service-account-private-key-file&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--feature-gates&lt;/code&gt; 启用 &lt;code&gt;RotateKubeletServerCertificate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Scheduler&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;关闭 &lt;code&gt;profiling&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;绑定 &lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;ETCD&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;启用节点间和客户端的双向认证&lt;/li&gt;
&lt;li&gt;设置数据文件权限&lt;/li&gt;
&lt;li&gt;禁用 &lt;code&gt;--auto-tls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用独立的 CA 证书&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;工作节点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubelet、Kube-proxy 的服务和配置文件权限&lt;/li&gt;
&lt;li&gt;关闭匿名访问&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--authorization-mode&lt;/code&gt;  禁用 &lt;code&gt;AlwaysAllow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;kubelet 参数 &lt;code&gt;--read-only-port&lt;/code&gt; 为 0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--streaming-connection-idle-timeout&lt;/code&gt; 不应设置为 0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--protect-kernel-defaults&lt;/code&gt; 设置为 &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--make-iptables-util-chains&lt;/code&gt; 设置为 &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;不要设置 &lt;code&gt;--hostname-override&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HTTPS 访问&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--rotate-certificates&lt;/code&gt; 设置为 &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--event-qps&lt;/code&gt; 设置足够高，或者为 &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--feature-gates&lt;/code&gt; 启用 &lt;code&gt;RotateKubeletServerCertificate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;RBAC 和 ServiceAccount&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;仅在需要时才使用 &lt;code&gt;cluster-admin&lt;/code&gt; 角色&lt;/li&gt;
&lt;li&gt;限制对 &lt;code&gt;secret&lt;/code&gt; 的访问&lt;/li&gt;
&lt;li&gt;限制使用通配符&lt;/li&gt;
&lt;li&gt;限制分配 Pod 创建权限&lt;/li&gt;
&lt;li&gt;仅在需要时才加载 Token，缺省将 automountServic eAccountToken 为 &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用不同的 ServiceAccount&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Pod Security Policy&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;使用 PSP 不应泛泛使用 &lt;code&gt;privileged&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用 PSP 谨慎控制如下授权

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hostPID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hostIPC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hostNetwork&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allowPrivilegeEscalation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runAsUser.rule&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NET_RAW&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;不应提供全面放行的 PSP 策略&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;网络策略和 CNI&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;支持策略的 CNI&lt;/li&gt;
&lt;li&gt;所有命名空间都定义网络策略&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Secret 管理&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;建议使用文件而非环境变量&lt;/li&gt;
&lt;li&gt;使用外部 Secret 存储&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;扩展准入控制&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;保障镜像来源&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;通用策略&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;命名空间隔离&lt;/li&gt;
&lt;li&gt;在 Docker 中启用 seccomp&lt;/li&gt;
&lt;li&gt;为 Pod 和容器启用 Security context&lt;/li&gt;
&lt;li&gt;不用缺省命名空间&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Loki Stack：乞丐版 EFK</title>
      <link>/post/es-express/</link>
      <pubDate>Wed, 12 Feb 2020 23:27:36 +0800</pubDate>
      <guid>/post/es-express/</guid>
      <description>

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;踩入 Kubernetes 的坑以后，不可避免的就会遇到一个需求就是日志的集中采集和检索，这方面最负盛名的就是 ElasticSearch 了，这东西的强大是毋庸置疑的——又强又大。但是我多数时间跟日志打交道只会问一个问题：特定时间范围内，某应用都输出了什么日志？强大的全文检索能力，其实是很少用到的。但无论你用或者不用，索引就在那里，吃你传输和硬盘。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://grafana.com/oss/loki/&#34; target=&#34;_blank&#34;&gt;Loki&lt;/a&gt; 给穷人们带来了福音，它是一个可水平伸缩的、高可用的以及多租户的日志集中系统，有这么多功能，唯独没有全文检索。在其简介中，自称是受到 Prometheus 的启发：仅保存和处理元数据，而对日志正文不闻不问。&lt;/p&gt;

&lt;p&gt;和 EFK 类似，Loki Stack 也由采集端、服务端和交互端三个部分构成，其中采集端是可变的，目前支持 Promtail、FluentBit 和 Fluentd 三种，服务端和交互端分别使用的是 Loki 和 Grafana。&lt;/p&gt;

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

&lt;p&gt;使用 Helm 可以非常方便的部署 Loki Stack，首先使用 &lt;code&gt;helm repo add loki https://grafana.github.io/loki/charts &amp;amp;&amp;amp; helm repo update&lt;/code&gt; 加入仓库并更新。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这里使用的是 Helm 3。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;helm install stable/grafana  \
    --set ingress.enabled=true \
    --set ingress.hosts={grafana.yamler.io} \
    --set persistence.enabled=true \
    --generate-name
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;首先安装 Grafana，并设置持久化存储以及 &lt;code&gt;grafana.yamler.io&lt;/code&gt; 的域名。&lt;/p&gt;

&lt;p&gt;接下来继续使用 Helm 安装 Loki：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;helm install loki/loki  \
    --set persistence.enabled=true \
    --set replicas=2 --generate-name
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里为 Loki 部署了两个实例的 Statefulset。并启用了持久化存储。&lt;/p&gt;

&lt;p&gt;最后安装 Fluentbit 作为日志采集端：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;helm install fluent-bit loki/fluent-bit \
--set &amp;quot;loki.serviceName=loki-1581527457&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;此处的服务名来自 Loki 部署的 Service 名称。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;初始化&#34;&gt;初始化&lt;/h2&gt;

&lt;p&gt;接下来登入 Grafana，添加 Loki 作为数据源，由于没有开启认证，因此只需要设置 URL 即可，例如：&lt;code&gt;http://loki-1581527457:3100&lt;/code&gt;。添加之后，就可以点击 Explore 按钮进入查询交互了。&lt;/p&gt;

&lt;h2 id=&#34;查询&#34;&gt;查询&lt;/h2&gt;

&lt;p&gt;Grafana 中为 Loki 提供了两种类型的查询，分别是 Metrics 和 Log，也就是统计数字和日志本体。&lt;/p&gt;

&lt;p&gt;Loki 使用一种称为 &lt;a href=&#34;https://github.com/grafana/loki/blob/master/docs/logql.md#filter-expression&#34; target=&#34;_blank&#34;&gt;LogQL&lt;/a&gt; 的语法来进行日志检索，语法类似 PromQL。例如假设要查询 Loki 的日志：&lt;code&gt;{app=&amp;quot;loki&amp;quot;}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/first-query.png&#34; alt=&#34;first&#34; /&gt;&lt;/p&gt;

&lt;p&gt;当然也可以使用 &lt;code&gt;node&lt;/code&gt;、&lt;code&gt;container&lt;/code&gt; 等标签进行查询。具体标签生成的过程完全在采集器内部进行定义。&lt;/p&gt;

&lt;p&gt;又或者查询一个 Metrics：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sum(count_over_time({job=&amp;quot;fluent-bit&amp;quot;}[5m])) by (level)&lt;/code&gt;，统计该任务的日志生成数量：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/count.png&#34; alt=&#34;count&#34; /&gt;&lt;/p&gt;

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

&lt;p&gt;除了上面演示的简单功能之外，Loki 还支持认证、监控、跟踪、多租户等丰富的功能（而且免费）。因此如果对全文检索没有执着需求的话，Loki 应该是个非常合适的替代方案，能很好很便宜地满足前面提到的简单需求。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个不太小的工具——Stash</title>
      <link>/post/intro-appcode-stash/</link>
      <pubDate>Fri, 31 Jan 2020 23:34:25 +0800</pubDate>
      <guid>/post/intro-appcode-stash/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://stash.run/&#34; target=&#34;_blank&#34;&gt;AppCode Stash&lt;/a&gt; 是一个构建在 &lt;a href=&#34;https://restic.net&#34; target=&#34;_blank&#34;&gt;Restic&lt;/a&gt; 基础之上的工具，用于备份 Kubernetes 上运行的有状态应用，它使用一个有趣的 Sidecar，能够方便的通过共享卷的方式来对 RWO 模式的存储卷进行备份。提供了批量备份、备份模板、可扩展的应用（主要是数据库）备份等功能。备份目标包括 Kubernetes 卷、S3 等常见设施。主要功能包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;备份和恢复工作负载数据：包括 Deploy、DaemonSet、StatefulSet 等等。&lt;/li&gt;
&lt;li&gt;备份和恢复独立存储卷。&lt;/li&gt;
&lt;li&gt;备份和恢复数据库：PostgreSQL、MySQL、MongoDB、ElasticSearch。&lt;/li&gt;
&lt;li&gt;卷快照：支持 CSI 卷快照功能的集群。&lt;/li&gt;
&lt;li&gt;计划备份。&lt;/li&gt;
&lt;li&gt;自动备份：使用注解和模板进行备份。&lt;/li&gt;
&lt;li&gt;多种备份存储目标：支持 S3、Azure、GCP 以及存储卷等多种设施。&lt;/li&gt;
&lt;li&gt;可监控：支持 Prometheus 监控。&lt;/li&gt;
&lt;li&gt;提供 kubectl 插件。&lt;/li&gt;
&lt;li&gt;可扩展开发。&lt;/li&gt;
&lt;li&gt;支持 hook 和 Webhook。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;使用 Helm &lt;strong&gt;3&lt;/strong&gt; 安装：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ helm repo add appscode https://charts.appscode.com/stable/
&amp;quot;appscode&amp;quot; has been added to your repositories
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the &amp;quot;appscode&amp;quot; chart repository
...
Update Complete. ⎈ Happy Helming!⎈
$ helm search repo appscode/stash --version v0.9.0-rc.4
NAME            CHART VERSION APP VERSION DESCRIPTION
appscode/stash  v0.9.0-rc.4    v0.9.0-rc.4  Stash by AppsCode - Backup your Kubernetes Volumes

$ helm install stash-operator appscode/stash \
  --version v0.9.0-rc.4 \
  --namespace kube-system
&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 pods --all-namespaces -l app=stash --watch
NAMESPACE     NAME                              READY     STATUS    RESTARTS   AGE
kube-system   stash-operator-859d6bdb56-m9br5   2/2       Running   2          5s
$ kubectl get crd -l app=stash
NAME                                 AGE
recoveries.stash.appscode.com        5s
repositories.stash.appscode.com      5s
restics.stash.appscode.com           5s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;安装完毕之后，就可以尝试第一次备份操作了。&lt;/p&gt;

&lt;h2 id=&#34;创建备份后端&#34;&gt;创建备份后端&lt;/h2&gt;

&lt;p&gt;这里简单使用一个 PVC 来做为备份存储的后端。&lt;/p&gt;

&lt;p&gt;Restic 的备份过程需要指定一个密码，这里使用一个 Secret 来保存密码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ echo -n &#39;changeit&#39; &amp;gt; RESTIC_PASSWORD
$ kubectl create secret generic backup-password --from-file=./RESTIC_PASSWORD
secret/local-secret created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来创建一个 PVC，作为备份文件的存储目标：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: backend-pvc
spec:
  resources:
    requests:
      storage: 8Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在 Stash 中新建一个 Repository，用刚才的 PVC 作为存储：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: stash.appscode.com/v1alpha1
kind: Repository
metadata:
  name: backend-repository
spec:
  backend:
    local:
      mountPath: /storage/data
      persistentVolumeClaim:
        claimName: backend-pvc
    storageSecretName: backup-password
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;此处的 &lt;code&gt;local&lt;/code&gt; 段，指明了在本地加载一个卷作为备份文件的存储目标。&lt;a href=&#34;https://stash.run/docs/v0.9.0-rc.4/guides/latest/&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;中介绍了各种存储后端的用法。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;storageSecretName&lt;/code&gt; 除了刚才提到的备份密码之外，还用于存储后端的认证凭据，例如使用 S3 后端就需要这样的 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ echo -n &#39;changeit&#39; &amp;gt; RESTIC_PASSWORD
$ echo -n &#39;&amp;lt;your-azure-storage-account-name&amp;gt;&#39; &amp;gt; AZURE_ACCOUNT_NAME
$ echo -n &#39;&amp;lt;your-azure-storage-account-key&amp;gt;&#39; &amp;gt; AZURE_ACCOUNT_KEY
$ kubectl create secret generic -n demo azure-secret \
    --from-file=./RESTIC_PASSWORD \
    --from-file=./AZURE_ACCOUNT_NAME \
    --from-file=./AZURE_ACCOUNT_KEY
secret/azure-secret created
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;创建工作负载&#34;&gt;创建工作负载&lt;/h2&gt;

&lt;p&gt;接下来随便运行一个应用，挂载 PVC 来模拟业务应用来进行备份。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
        version: v1
    spec:
      containers:
        - name: sleep
          image: dustise/sleep
          imagePullPolicy: IfNotPresent
          resources:
            limits:
              cpu: 100m
              memory: 100M
            requests:
              cpu: 100m
              memory: 100M
          volumeMounts:
          - name: storage
            mountPath: /data
      volumes:
        - name: storage
          persistentVolumeClaim:
            claimName: workload-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: workload-pvc
spec:
  resources:
    requests:
      storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 正常运行后，生成一个文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
sleep-76b447c854-7xmgf   1/1     Running   0          9m17s
$ kubectl exec -it sleep-76b447c854-7xmgf -c sleep -- fallocate -l 5M /data/file-5m.txt
$ kubectl exec -it sleep-76b447c854-7xmgf -c sleep ls /data
file-5m.txt     lost+found
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;备份&#34;&gt;备份&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;BackupConfiguration&lt;/code&gt; 是一个 CRD，用于连接工作负载和备份存储目标。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: stash.appscode.com/v1beta1
kind: BackupConfiguration
metadata:
  name: backupconfig-sleep
spec:
  repository:
    name: backend-repository
  # 每五分钟一次备份
  schedule: &amp;quot;*/5 * * * *&amp;quot;
  target:
    ref:
      apiVersion: apps/v1
      kind: Deployment
      name: sleep
    # 加载工作负载中的卷
    volumeMounts:
    - name: storage
      mountPath: /data
    paths:
    - /data
  retentionPolicy:
    name: &#39;keep-last-5&#39;
    keepLast: 5
    prune: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建之后，会发现业务 Pod 被注入了 Sidecar，&lt;strong&gt;正在重启&lt;/strong&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods
NAME                     READY   STATUS              RESTARTS   AGE
sleep-569b786766-mwmrb   0/2     ContainerCreating   0          3s
sleep-76b447c854-7bvtz   1/1     Running             0          10m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;并且系统中出现了新的 CronJob 和 BackupSession 对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get cronjob
NAME                              SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
stash-backup-backupconfig-sleep   */5 * * * *   False     0        4m31s           36m

$ kubectl get backupsession
NAME                            INVOKER-TYPE          INVOKER-NAME         PHASE       AGE
backupconfig-sleep-1580535011   BackupConfiguration   backupconfig-sleep   Succeeded   39s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面看到，这个 BackupSession 已经成功了一次，就可以看看 Repository 有没有什么变化了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get repository
NAME                 INTEGRITY   SIZE   SNAPSHOT-COUNT   LAST-SUCCESSFUL-BACKUP   AGE
backend-repository   true               5                4m58s                    61m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Describe 一下这个对象，会看到其中包含的快照数量，以及文件尺寸。&lt;/p&gt;

&lt;h2 id=&#34;恢复&#34;&gt;恢复&lt;/h2&gt;

&lt;p&gt;备份之后，我们新建一个 Deployment，作为恢复的目标：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep-restore
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleep-restore
  template:
    metadata:
      labels:
        app: sleep-restore
        version: v1
    spec:
      containers:
        - name: sleep-restore
          image: dustise/sleep
          imagePullPolicy: IfNotPresent
          resources:
            limits:
              cpu: 100m
              memory: 100M
            requests:
              cpu: 100m
              memory: 100M
          volumeMounts:
          - name: storage
            mountPath: /data
      volumes:
        - name: storage
          persistentVolumeClaim:
            claimName: restore-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restore-pvc
spec:
  resources:
    requests:
      storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后创建一个 RestoreSession 对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: stash.appscode.com/v1beta1
kind: RestoreSession
metadata:
  name: deployment-restore
spec:
  repository:
    name: backend-repository
  rules:
  - paths:
    - /data
  target:
    ref:
      apiVersion: apps/v1
      kind: Deployment
      name: sleep-restore
    volumeMounts:
    - name:  storage
      mountPath: /data
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 kubectl 提交对象，启动还原过程。&lt;/p&gt;

&lt;p&gt;通过 &lt;code&gt;kubectl get po&lt;/code&gt; 的观察，可以看到新建的 Pod 也重启了，这次加入的不是 Sidecar，而是一个 init-container。容器重建之后，可以进入 Pod 查看，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl exec -it sleep-restore-76ff947f9b-s52px ls /data
file-5m.txt     lost+found
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;其它&#34;&gt;其它&lt;/h2&gt;

&lt;p&gt;除了 Deployment 之外，Stash 还支持 Statefulset、Daemonset 的备份。另外能通过 AppBind 和 Task 等对象完成针对特定数据库的备份和恢复；通过 BackupBatch 来实现一个应用多种数据的备份；最后更可以使用 BackupPrint 对象根据 Annotation 进行自动备份。&lt;/p&gt;

&lt;p&gt;缺点也是有的：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;目前只是 RC 版本。&lt;/li&gt;
&lt;li&gt;后面提到的几种功能复杂性都比本文演示的 Deployment 备份要复杂得多。&lt;/li&gt;
&lt;li&gt;文档非常好，但是不够完善，并且略有瑕疵。&lt;/li&gt;
&lt;li&gt;注入过程会引发业务中断。&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;用在生产环境还是颇为冒险的，好在备份恢复从来就不是个容易的事情，这东西至少有很好的参考价值。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 权威指南第二章校对（3）</title>
      <link>/post/read-k8s-guide-2-3/</link>
      <pubDate>Thu, 30 Jan 2020 23:12:47 +0800</pubDate>
      <guid>/post/read-k8s-guide-2-3/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;春节宅在家伺候小祖宗，可惜老外们不休息，新东西一坨接着一坨，安装部分又超级没有技术含量，因此疯狂拖延。今天终于把二进制安装过程的 API Server 部分弄好。
这部分还只有一个 API Server，后续内容的推进过程中，还有很大的可能发生变化。
太讨厌了——第二章可能是个修身养性的章节。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;首先是觉得在 service 里面引用环境变量的方式似乎没有什么特别的好处。因此丢弃了环境变量文件，直接把参数写入了 Service 文件。&lt;/p&gt;

&lt;p&gt;其次，匿名访问、http 访问都是严重不建议的方式，现稿虽然后面加入了证书相关内容，难免造成误导，这里直接把证书内容加入到每个组件的安装过程之中，让读者在跳着读时候，也能够收到一个相对完整的效果。&lt;/p&gt;

&lt;p&gt;更新后的 &lt;code&gt;kube-apiserver.service&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=etcd.service
Wants=etcd.service
[Service]
ExecStart=/usr/bin/kube-apiserver \
--allow-privileged=true \
--authorization-mode=Node,RBAC \
--client-ca-file=/etc/kubernetes/pki/ca.crt \
--enable-admission-plugins=NodeRestriction \
--enable-bootstrap-token-auth=true \
--etcd-cafile=/etc/kubernetes/pki/ca.crt \
--etcd-certfile=/etc/kubernetes/pki/etcd-client.crt \
--etcd-keyfile=/etc/kubernetes/pki/etcd-client.key \
--etcd-servers=https://127.0.0.1:2379 \
--insecure-port=0 \
--kubelet-client-certificate=/etc/kubernetes/pki/kubelet-client.crt \
--kubelet-client-key=/etc/kubernetes/pki/kubelet-client.key \
--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname \
--secure-port=6443 \
--service-cluster-ip-range=10.96.0.0/12 \
--tls-cert-file=/etc/kubernetes/pki/apiserver.crt \
--tls-private-key-file=/etc/kubernetes/pki/apiserver.key
Restart=on-failure
Type=notify
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;kube-apiserver 需要一系列的数字证书才能启动。必要的证书参数包括：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;--client-ca-file&lt;/code&gt;：客户端（例如 kubectl）使用客户端证书作为凭据访问 apiserver 时，用于签发客户端证书的 ca 证书文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--etcd-cafile&lt;/code&gt;、&lt;code&gt;--etcd-certfile&lt;/code&gt;、&lt;code&gt;--etcd-keyfile&lt;/code&gt;：apiserver 作为客户端，使用 https 协议访问 etcd 时，所信任的 ca 证书文件，以及客户端证书文件和密钥。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--kubelet-client-certificate&lt;/code&gt;、&lt;code&gt;--kubelet-client-key&lt;/code&gt;：apiserver 访问 kubelet 时所使用的证书文件和密钥。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--tls-cert-file&lt;/code&gt;、&lt;code&gt;--tls-private-key-file&lt;/code&gt;：apiserver 提供 https 服务时所使用的证书文件和密钥。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;etcd 一节中，我们已经了解了服务器证书的生成方法，这里为了简单起见，把所有用于签发证书的 ca 证书统一为前面生成过的 &lt;code&gt;/etc/kubernetes/pki/ca.crt&lt;/code&gt;。几个证书参数中，只有 &lt;code&gt;--tls-cert-file&lt;/code&gt; 是和前面的 &lt;code&gt;etcd-server.crt&lt;/code&gt; 一样的https 服务端证书，生成方法是一样的，差别在于 &lt;code&gt;[alt_names]&lt;/code&gt; 字段，其中除了包括服务器的 IP 之外，还包含了几个 DNS 名称，代表的是缺省的 Kubernetes 服务，摘录如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster.local
IP.1 = 10.211.55.33
IP.2 = 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用这个cnf文件生成apiserver的服务端密钥和证书文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# openssl genrsa -out apiserver.key 2048
# openssl req -new -key apiserver.key -subj &amp;quot;/CN=kubernetes&amp;quot; -config apiserver.cnf -out apiserver.csr
# openssl x509 -req -in apiserver.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out apiserver.crt -days 365 -extensions v3_req -extfile apiserver.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这些命令执行成功后，生成 &lt;code&gt;apiserver.key&lt;/code&gt; 和 &lt;code&gt;apiserver.crt&lt;/code&gt; 两个文件，这两个证书将用于 apiserver 的&lt;code&gt;--tls-private-key-file&lt;/code&gt;、&lt;code&gt;--tls-cert-file&lt;/code&gt; 两个参数。&lt;/p&gt;

&lt;p&gt;生成 apiserver 的服务端证书后，还需要为 etcd 和 kubelet 生成客户端证书。客户端证书相对简单，主要关注的是 &lt;code&gt;-subj&lt;/code&gt; 参数。&lt;/p&gt;

&lt;p&gt;首先是 etcd 的客户端证书，设置 &lt;code&gt;CN=apiserver，OU=kubernetes&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# openssl genrsa -out etcd-client.key 2048
# openssl req -new -out etcd-client.csr -key etcd-client.key -subj &amp;quot;/CN=apiserver/OU=kubernetes&amp;quot;
# openssl x509 -req -in etcd-client.csr -out etcd-client.crt -signkey etcd-client.key -CA ca.crt -CAkey ca.key -CAcreateserial -days 365
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;类似地，生成 kubelet 客户端证书，和 etcd 客户端类似，不同是的 &lt;code&gt;-subj&lt;/code&gt; 参数，命令如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# openssl genrsa -out kubelet-client.key 2048
# openssl req -new -out kubelet-client.csr -key kubelet-client.key -subj &amp;quot;/CN= kube-apiserver-kubelet-client /OU= system:masters&amp;quot;
# openssl x509 -req -in kubelet-client.csr -out kubelet-client.crt -signkey kubelet-client.key -CA ca.crt -CAkey ca.key -CAcreateserial -days 365
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;service文件和一系列的证书都生成完毕之后，用如下命令启动kube-apiserver服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# systemctl daemon-reload
# systemctl start kube-apiserver
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;服务启动之后，可以使用 curl 尝试访问 apiserver 的端口：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# curl https://127.0.0.1:6443
{
  &amp;quot;kind&amp;quot;: &amp;quot;Status&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;metadata&amp;quot;: {

  },
  &amp;quot;status&amp;quot;: &amp;quot;Failure&amp;quot;,
  &amp;quot;message&amp;quot;: &amp;quot;forbidden: User \&amp;quot;system:anonymous\&amp;quot; cannot get path \&amp;quot;/\&amp;quot;&amp;quot;,
  &amp;quot;reason&amp;quot;: &amp;quot;Forbidden&amp;quot;,
  &amp;quot;details&amp;quot;: {

  },
  &amp;quot;code&amp;quot;: 403
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;返回了鉴权失败的信息，证明 apiserver 在正常运行了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>浅谈 Kubernetes 中的服务发现</title>
      <link>/post/demystifying-kubernetes-service-discovery/</link>
      <pubDate>Wed, 29 Jan 2020 09:42:04 +0800</pubDate>
      <guid>/post/demystifying-kubernetes-service-discovery/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://nigelpoulton.com/blog/f/demystifying-kubernetes-service-discovery&#34; target=&#34;_blank&#34;&gt;Demystifying Kubernetes service discovery&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://twitter.com/nigel_poulton&#34; target=&#34;_blank&#34;&gt;Nigel Poulton&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 服务发现是一个经常让我产生困惑的主题之一。本文分为两个部分：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;网络方面的背景知识&lt;/li&gt;
&lt;li&gt;深入了解 Kubernetes 服务发现&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要了解服务发现，首先要了解背后的网络知识。这部分内容相对浅显，如果读者熟知这一部分，完全可以跳过，直接阅读服务发现部分。&lt;/p&gt;

&lt;p&gt;开始之前还有一个需要提醒的事情就是，为了详细描述这一过程，本文略长。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-网络基础&#34;&gt;Kubernetes 网络基础&lt;/h2&gt;

&lt;p&gt;要开始服务发现的探索之前，需要理解以下内容：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Kubernetes 应用运行在容器之中，容器处于 Pod 之内。&lt;/li&gt;
&lt;li&gt;每个 Pod 都会附着在同一个大的扁平的 IP 网络之中，被称为 Pod 网络（通常是 VXLAN 叠加网络）。&lt;/li&gt;
&lt;li&gt;每个 Pod 都有自己的唯一的 IP 地址，这个 IP 地址在 Pod 网络中是可路由的。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&#34;images/pod-network2.png&#34; alt=&#34;Pod Network&#34; /&gt;&lt;/p&gt;

&lt;p&gt;上述三个因素结合起来，让每个应用（应用的组件和服务）无需通过 NAT 之类的网络过程，就能够直接通信。&lt;/p&gt;

&lt;h3 id=&#34;动态网络&#34;&gt;动态网络&lt;/h3&gt;

&lt;p&gt;在对应用进行横向扩容时，会在 Pod 网络中加入新的 Pod，新 Pod 自然也伴随着新的 IP 地址；如果对应用进行缩容，旧的 Pod 及其 IP 会被删除。这个过程看起来很是混乱。&lt;/p&gt;

&lt;p&gt;应用的滚动更新和撤回也存在同样的情形——加入新版本的新 Pod，或者移除旧版本的旧 Pod。新 Pod 会加入新 IP 到 Pod 网络中，被终结的旧 Pod 会删除其现存 IP。&lt;/p&gt;

&lt;p&gt;如果没有其它因素，每个应用服务都需要对网络进行监控，并管理一个健康 Pod 的列表。这个过程会非常痛苦，另外在每个应用中编写这个逻辑也是很低效的。幸运的是，Kubernetes 用一个对象完成了这个过程——Service。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;把这个对象叫做 Service 是个坏主意，我们已经用这个单词来形容应用的进程或组件了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;还有一个值得注意的事情：Kubernetes 执行 IP 地址管理（IPAM）职责，对 Pod 网络上已使用和可用的 IP 地址进行跟踪。&lt;/p&gt;

&lt;h3 id=&#34;service-带来稳定性&#34;&gt;Service 带来稳定性&lt;/h3&gt;

&lt;p&gt;Kubernetes Service 对象在一组提供服务的 Pod 之前创建一个稳定的网络端点，并为这些 Pod 进行负载分配。&lt;/p&gt;

&lt;p&gt;一般会在一组完成同样工作的 Pod 之前放置一个 Service 对象。例如可以在你的 Web 前端 Pod 前方提供一个 Service，在认证服务 Pod 之前提供另一个。行使不同职责的 Pod 之前就不应该用单一的 Service 了。&lt;/p&gt;

&lt;p&gt;客户端和 Service 通信，Service 负责把流量负载均衡给 Pod。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/service-101.png&#34; alt=&#34;Service 101&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在上图中，底部的 Pod 会因为伸缩、更新、故障等情况发生变化，而 Service 会对这些变化进行跟踪。同时 Service 的名字、IP 和端口都不会发生变化。&lt;/p&gt;

&lt;h3 id=&#34;kubernetes-service-解析&#34;&gt;Kubernetes Service 解析&lt;/h3&gt;

&lt;p&gt;可以把 Kubernetes Service 理解为前端和后端两部分：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;前端：名称、IP 和端口等不变的部分。&lt;/li&gt;
&lt;li&gt;后端：符合特定标签选择条件的 Pod 集合。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;前端是稳定可靠的，它的名称、IP 和端口在 Service 的整个生命周期中都不会改变。前端的稳定性意味着无需担心客户端 DNS 缓存超时等问题。&lt;/p&gt;

&lt;p&gt;后端是高度动态的，其中包括一组符合标签选择条件的 Pod，会通过负载均衡的方式进行访问。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/service-anatomy3.png&#34; alt=&#34;Service Anatomy&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这里的负载均衡是一个简单的 4 层轮询。它工作在连接层面，所以同一个连接里发起的所有请求都会进入同一个 Pod。因为在 4 层工作，所以对于 7 层的 HTTP 头或者 Cookie 之类的东西是无法感知的。&lt;/p&gt;

&lt;h3 id=&#34;小结&#34;&gt;小结&lt;/h3&gt;

&lt;p&gt;应用在容器中运行，在 Kubernetes 中体现为 Pod 的形式。Kubernetes 集群中的所有 Pod 都处于同一个平面的 Pod 网络，有自己的 IP 地址。这意味着所有的 Pod 之间都能直接连接。然而 Pod 是不稳定的，可能因为各种因素创建和销毁。Kubernetes 提供了稳定的网络端点，称为 Service，这个对象处于一组相似的 Pod 前方，提供了稳定的名称、IP 和端口。客户端连接到 Service，Service 把流量负载均衡给 Pod。&lt;/p&gt;

&lt;p&gt;接下来聊聊服务发现。&lt;/p&gt;

&lt;h2 id=&#34;深入了解-kubernetes-服务发现&#34;&gt;深入了解 Kubernetes 服务发现&lt;/h2&gt;

&lt;p&gt;服务发现实际上包含两个功能点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;服务注册&lt;/li&gt;
&lt;li&gt;服务发现&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;服务注册&#34;&gt;服务注册&lt;/h3&gt;

&lt;p&gt;服务注册过程指的是在服务注册表中登记一个服务，以便让其它服务发现。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/service-registration.png&#34; alt=&#34;service-registration.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 使用 DNS 作为服务注册表。&lt;/p&gt;

&lt;p&gt;为了满足这一需要，每个 Kubernetes 集群都会在 &lt;code&gt;kube-system&lt;/code&gt; 命名空间中用 Pod 的形式运行一个 DNS 服务，通常称之为集群 DNS。&lt;/p&gt;

&lt;p&gt;每个 Kubernetes 服务都会自动注册到集群 DNS 之中。&lt;/p&gt;

&lt;p&gt;注册过程大致如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;向 API Server 用 POST 方式提交一个新的 Service 定义；&lt;/li&gt;
&lt;li&gt;这个请求需要经过认证、鉴权以及其它的准入策略检查过程之后才会放行；&lt;/li&gt;
&lt;li&gt;Service 得到一个 &lt;code&gt;ClusterIP&lt;/code&gt;（虚拟 IP 地址），并保存到集群数据仓库；&lt;/li&gt;
&lt;li&gt;在集群范围内传播 Service 配置；&lt;/li&gt;
&lt;li&gt;集群 DNS 服务得知该 Service 的创建，据此创建必要的 DNS A 记录。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;上面过程中，第 5 个步骤是关键环节。集群 DNS 使用的是 CoreDNS，以 Kubernetes 原生应用的形式运行。CoreDNS 实现了一个控制器，会对 API Server 进行监听，一旦发现有新建的 Service 对象，就创建一个从 Service 名称映射到 ClusterIP 的域名记录。这样 Service 就不必自行向 DNS 进行注册，CoreDNS 控制器会关注新创建的 Service 对象，并实现后续的 DNS 过程。&lt;/p&gt;

&lt;p&gt;DNS 中注册的名称就是 &lt;code&gt;metadata.name&lt;/code&gt;，而 ClusterIP 则由 Kubernetes 自行分配。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/svc-yaml2.png&#34; alt=&#34;svc-yaml2&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Service 对象注册到集群 DNS 之中后，就能够被运行在集群中的其它 Pod 发现了。&lt;/p&gt;

&lt;h4 id=&#34;endpoint-对象&#34;&gt;Endpoint 对象&lt;/h4&gt;

&lt;p&gt;Service 的前端创建成功并注册到服务注册表（DNS）之后，剩下的就是后端的工作了。后端包含一个 Pod 列表，Service 对象会把流量分发给这些 Pod。&lt;/p&gt;

&lt;p&gt;毫无疑问，这个 Pod 列表需要是最新的。&lt;/p&gt;

&lt;p&gt;Service 对象有一个 Label Selector 字段，这个字段是一个标签列表，符合列表条件的 Pod 就会被服务纳入到服务的负载均衡范围之中。参见下图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/backend2.png&#34; alt=&#34;backend2&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 自动为每个 Service 创建 Endpoints 对象。Endpoints 对象的职责就是保存一个符合 Service 标签选择器标准的 Pod 列表，这些 Pod 将接收来自 Service 的流量。&lt;/p&gt;

&lt;p&gt;下面的图中，Service 会选择两个 Pod，并且还展示了 Service 的 Endpoints 对象，这个对象里包含了两个符合 Service 选择标准的 Pod 的 IP。&lt;/p&gt;

&lt;p&gt;在后面我们将解释网络如何把 ClusterIP 流量转发给 Pod IP 的过程，还会引用到 Endpoints 对象。&lt;/p&gt;

&lt;h3 id=&#34;服务发现&#34;&gt;服务发现&lt;/h3&gt;

&lt;p&gt;假设我们在一个 Kubernetes 集群中有两个应用，&lt;code&gt;my-app&lt;/code&gt; 和 &lt;code&gt;your-app&lt;/code&gt;，&lt;code&gt;my-app&lt;/code&gt; 的 Pod 的前端是一个 名为 &lt;code&gt;my-app-svc&lt;/code&gt; 的 Service 对象；&lt;code&gt;your-app&lt;/code&gt; Pod 之前的 Service 就是 &lt;code&gt;your-app-svc&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这两个 Service 对象对应的 DNS 记录是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;my-app-svc&lt;/code&gt;：10.0.0.10&lt;/li&gt;
&lt;li&gt;&lt;code&gt;your-app-svc&lt;/code&gt;：10.0.0.20&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;images/dns-reg.png&#34; alt=&#34;dns-reg&#34; /&gt;&lt;/p&gt;

&lt;p&gt;要使用服务发现功能，每个 Pod 都需要知道集群 DNS 的位置才能使用它。因此每个 Pod 中的每个容器的 &lt;code&gt;/etc/resolv.conf&lt;/code&gt; 文件都被配置为使用集群 DNS 进行解析。&lt;/p&gt;

&lt;p&gt;如果 &lt;code&gt;my-app&lt;/code&gt; 中的 Pod 想要连接到 &lt;code&gt;your-app&lt;/code&gt; 中的 Pod，就得向 DNS 服务器发起对域名 &lt;code&gt;your-app-svc&lt;/code&gt; 的查询。假设它们本地的 DNS 解析缓存中没有这个记录，则需要把查询提交到集群 DNS 服务器。会得到 &lt;code&gt;you-app-svc&lt;/code&gt; 的 ClusterIP（VIP）。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这里有个前提就是 &lt;code&gt;my-app&lt;/code&gt; 需要知道目标服务的名称。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;至此，&lt;code&gt;my-app&lt;/code&gt; 中的 Pod 得到了一个目标 IP 地址，然而这只是个虚拟 IP，在转入目标 Pod 之前，还有些网络工作要做。&lt;/p&gt;

&lt;h4 id=&#34;网络&#34;&gt;网络&lt;/h4&gt;

&lt;p&gt;一个 Pod 得到了 Service 的 ClusterIP 之后，就尝试向这个 IP 发送流量。然而 ClusterIP 所在的网络被称为 &lt;code&gt;Service Network&lt;/code&gt;，这个网络有点特别——没有路由指向它。&lt;/p&gt;

&lt;p&gt;因为没有路由，所有容器把发现这种地址的流量都发送到了缺省网关（名为 &lt;code&gt;CBR0&lt;/code&gt; 的网桥）。这些流量会被转发给 Pod 所在节点的网卡上。节点的网络栈也同样没有路由能到达 Service Network，所以只能发送到自己的缺省网关。路由到节点缺省网关的数据包会通过 Node 内核——这里有了变化。&lt;/p&gt;

&lt;p&gt;回顾一下前面的内容。首先 Service 对象的配置是全集群范围有效的，另外还会再次说到 Endpoints 对象。我们要在回顾中发现他们各自在这一过程中的职责。&lt;/p&gt;

&lt;p&gt;每个 Kubernetes 节点上都会运行一个叫做 &lt;code&gt;kube-proxy&lt;/code&gt; 的系统服务。这是一个基于 Pod 运行的 Kubernetes 原生应用，它所实现的控制器会监控 API Server 上 Service 的变化，并据此创建 &lt;code&gt;iptables&lt;/code&gt; 或者 &lt;code&gt;IPVS&lt;/code&gt; 规则，这些规则告知节点，捕获目标为 Service 网络的报文，并转发给 Pod IP。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;有趣的是，kube-proxy 并不是一个普遍意义上的代理。它的工作不过是创建和管理 iptables/IPVS 规则。这个命名的原因是它过去使用 unserspace 模式的代理。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;每个新 Service 对象的配置，其中包含它的 ClusterIP 以及 Endpoints 对象（其中包含健康 Pod 的列表），都会被发送给 每个节点上的 kube-proxy 进程。kube-proxy 会创建 iptables 或者 IPVS 规则，告知节点捕获目标为 Service ClusterIP 的流量，并根据 Endpoints 对象的内容转发给对应的 Pod。&lt;/p&gt;

&lt;p&gt;也就是说每次节点内核处理到目标为 Service 网络的数据包时，都会对数据包的 Header 进行改写，把目标 IP 改为 Service Endpoints 对象中的健康 Pod 的 IP。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;原本使用的 iptables 正在被 IPVS 取代（Kubernetes 1.11 进入稳定期）。长话短说，iptables 是一个包过滤器，并非为负载均衡设计的。IPVS 是一个 4 层的负载均衡器，其性能和实现方式都比 iptables 更适合这种使用场景。&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;需要消化的内容很多，简单回顾一下。&lt;/p&gt;

&lt;p&gt;创建新的 Service 对象时，会得到一个虚拟 IP，被称为 ClusterIP。服务名及其 ClusterIP 被自动注册到集群 DNS 中，并且会创建相关的 Endpoints 对象用于保存符合标签条件的健康 Pod 的列表，Service 对象会向列表中的 Pod 转发流量。&lt;/p&gt;

&lt;p&gt;与此同时集群中所有节点都会配置相应的 iptables/IPVS 规则，监听目标为 ClusterIP 的流量并转发给真实的 Pod IP。这个过程如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/registeration-flow.png&#34; alt=&#34;service register&#34; /&gt;&lt;/p&gt;

&lt;p&gt;一个 Pod 需要用 Service 连接其它 Pod。首先向集群 DNS 发出查询，把 Service 名称解析为 ClusterIP，然后把流量发送给位于 Service 网络的 ClusterIP 上。然而没有到 Service 网络的路由，所以 Pod 把流量发送给它的缺省网关。这一行为导致流量被转发给 Pod 所在节点的网卡，然后是节点的缺省网关。这个操作中，节点的内核修改了数据包 Header 中的目标 IP，使其转向健康的 Pod。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/discovery-flow.png&#34; alt=&#34;discovery&#34; /&gt;&lt;/p&gt;

&lt;p&gt;最终所有 Pod 都是在同一个可路由的扁平的叠加网络上，剩下的内容就很简单了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>CSI 的内联暂存卷</title>
      <link>/post/csi-ephemeral-inline-volumes/</link>
      <pubDate>Fri, 24 Jan 2020 14:35:19 +0800</pubDate>
      <guid>/post/csi-ephemeral-inline-volumes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2020/01/21/csi-ephemeral-inline-volumes/&#34; target=&#34;_blank&#34;&gt;CSI Ephemeral Inline Volumes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://github.com/pohly&#34; target=&#34;_blank&#34;&gt;Patrick Ohly&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 利用外部存储驱动提供出来的存储卷一般来说都是持久化的，它的生命周期可以完全独立于 Pod，（特定情况下）也可以和第一个用到该卷的 Pod（&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/storage-classes/#volume-binding-mode&#34; target=&#34;_blank&#34;&gt;后绑定模式&lt;/a&gt;）有着宽松的耦合关系。在 Kubernetes 中使用 PVC 和 PV 对象完成了存储卷的申请和供给机制。起初，容器存储接口（CSI）支持的存储卷只能用于 PVC/PV 的场合。&lt;/p&gt;

&lt;p&gt;但有些情况下，数据卷的内容和生命周期是和 Pod 紧密相关的。例如有的驱动会使用动态的创建 Secret 生成卷，这个 Secret 是为了运行在 Pod 中的应用特意创建的。这种卷需要和 Pod 一起生成，并且作为 Pod 的一部分，和 Pod 一起终结。可以在 Pod Spec 中（用内联/inline 的方式）定义这种卷。&lt;/p&gt;

&lt;p&gt;从 Kubernetes 1.15 开始，CSI 驱动也能用于这种内联暂存卷了。这个功能还处于 Alpha 阶段，因此在 1.15 中需要打开 &lt;a href=&#34;https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/&#34; target=&#34;_blank&#34;&gt;CSIInlineVolume 特性开关&lt;/a&gt; 才能尝试使用这一新功能。而 1.16 中，该功能升级为 Beta 阶段，因此是缺省打开的。&lt;/p&gt;

&lt;p&gt;虽说这个功能用到了两个现存的 gRPC 调用（&lt;code&gt;NodePublishVolume&lt;/code&gt; 和 &lt;code&gt;NodeUnpublishVolume&lt;/code&gt;)，但是其中的用法和 CSI 规范并不一致：在暂存卷中，Kubelet 在向 CSI 驱动请求卷时，只调用了 &lt;code&gt;NodePublishVolume&lt;/code&gt;。跳过了其他的（例如 &lt;code&gt;CreateVolume&lt;/code&gt;、&lt;code&gt;NodeStageVolume&lt;/code&gt;）调用，所以要对 CSI 驱动进行一些改变。Pod Spec 中写明了卷参数，这个参数会被拷贝给 &lt;code&gt;NodePublishVolumeRequest.volume_context&lt;/code&gt;。目前没有标准化的参数，即使是容量这种参数也是定义在 CSI 驱动之中的。类似地，在 Pod 结束需要释放存储卷时，只调用了 &lt;code&gt;NodeUnpublishVolume&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;起初有考虑分别为持久化和暂存卷编写不同的 CSI 驱动。但是有些驱动提供的存储在两种模式下都可以使用，例如 &lt;a href=&#34;https://github.com/intel/pmem-csi&#34; target=&#34;_blank&#34;&gt;PMEM-CSI&lt;/a&gt; 管理的是由 Intel Optane 技术提供的持久化内存方式的本地存储。这种类型的存储既可以用作一种比普通 SSD 更快的持久化存储，也可以用作比 DRAM 更大容量的暂时性存储。&lt;/p&gt;

&lt;p&gt;因此在 Kubernetes 1.16 中产生了变化：用户可以使用 &lt;a href=&#34;https://kubernetes-csi.github.io/docs/csi-driver-object.html#what-fields-does-the-csidriver-object-have&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;CSIDriver&lt;/code&gt;&lt;/a&gt; 的 &lt;code&gt;volumeLifecycleModes&lt;/code&gt; 字段来确定该驱动支持的卷类型。启用&lt;a href=&#34;https://kubernetes-csi.github.io/docs/pod-info.html&#34; target=&#34;_blank&#34;&gt;加载时 Pod 信息&lt;/a&gt;功能之后，驱动程序能够获取卷模式的信息，并在 &lt;code&gt;NodePublishRequest.volume_context&lt;/code&gt; 加入 &lt;code&gt;csi.storage.k8s.io/ephemeral&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;关于 CSI 驱动支持内联暂存卷的更多信息，可以浏览 &lt;a href=&#34;https://kubernetes-csi.github.io/docs/ephemeral-local-volumes.html&#34; target=&#34;_blank&#34;&gt;Kubernetes CSI 文档&lt;/a&gt; 及其&lt;a href=&#34;https://github.com/kubernetes/enhancements/blob/master/keps/sig-storage/20190122-csi-inline-volumes.md&#34; target=&#34;_blank&#34;&gt;原始设计文档&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;后续内容中包含了真实的示例以及内容总结。&lt;/p&gt;

&lt;h2 id=&#34;示例&#34;&gt;示例&lt;/h2&gt;

&lt;h3 id=&#34;pmem-csi&#34;&gt;PMEM-CSI&lt;/h3&gt;

&lt;p&gt;在 &lt;a href=&#34;https://github.com/intel/pmem-csi/releases/tag/v0.6.0&#34; target=&#34;_blank&#34;&gt;v0.6.0&lt;/a&gt; 中加入了内联暂存的支持。在使用 Intel Optane 技术的主机上可以使用这种驱动，&lt;a href=&#34;https://github.com/intel/pmem-csi/blob/v0.6.0/examples/gce.md&#34; target=&#34;_blank&#34;&gt;GCE 的特定类型服务器&lt;/a&gt;或者 QEMU 的硬件模拟上都是可用的。&lt;a href=&#34;https://github.com/intel/pmem-csi/tree/v0.6.0#qemu-and-kubernetes&#34; target=&#34;_blank&#34;&gt;QEMU 方式已经集成到了 Makefile&lt;/a&gt;，只需要 Go、Docker 和 KVM 即可，所以示例中用了这种方式：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;git clone --branch release-0.6 https://github.com/intel/pmem-csi
cd pmem-csi
TEST_DISTRO=clear TEST_DISTRO_VERSION=32080 TEST_PMEM_REGISTRY=intel make start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;启动四节点集群需要一些时间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;The test cluster is ready. Log in with /work/pmem-csi/_work/pmem-govm/ssh-pmem-govm, run kubectl once logged in.
Alternatively, KUBECONFIG=/work/pmem-csi/_work/pmem-govm/kube.config can also be used directly.

To try out the pmem-csi driver persistent volumes:
...

To try out the pmem-csi driver ephemeral volumes:
   cat deploy/kubernetes-1.17/pmem-app-ephemeral.yaml | /work/pmem-csi/_work/pmem-govm/ssh-pmem-govm kubectl create -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;deploy/kubernetes-1.17/pmem-app-ephemeral.yaml&lt;/code&gt; 定义了一个卷：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Pod
apiVersion: v1
metadata:
  name: my-csi-app-inline-volume
spec:
  containers:
    - name: my-frontend
      image: busybox
      command: [ &amp;quot;sleep&amp;quot;, &amp;quot;100000&amp;quot; ]
      volumeMounts:
      - mountPath: &amp;quot;/data&amp;quot;
        name: my-csi-volume
  volumes:
  - name: my-csi-volume
    csi:
      driver: pmem-csi.intel.com
      fsType: &amp;quot;xfs&amp;quot;
      volumeAttributes:
        size: &amp;quot;2Gi&amp;quot;
        nsmode: &amp;quot;fsdax&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 启动之后，可以观察一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl describe pods/my-csi-app-inline-volume
Name:         my-csi-app-inline-volume
...
Volumes:
  my-csi-volume:
    Type:              CSI (a Container Storage Interface (CSI) volume source)
    Driver:            pmem-csi.intel.com
    FSType:            xfs
    ReadOnly:          false
    VolumeAttributes:      nsmode=fsdax
                           size=2Gi
$ kubectl exec my-csi-app-inline-volume -- df -h /data
Filesystem                Size      Used Available Use% Mounted on
/dev/ndbus0region0fsdax/d7eb073f2ab1937b88531fce28e19aa385e93696
                          1.9G     34.2M      1.8G   2% /data
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;image-populator&#34;&gt;Image Populator&lt;/h3&gt;

&lt;p&gt;自动解包容器镜像，并以暂存卷的方式访问内容。这个驱动还在开发之中，但是可以用下面的方式安装试用镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;kubectl create -f https://github.com/kubernetes-csi/csi-driver-image-populator/raw/master/deploy/kubernetes-1.16/csi-image-csidriverinfo.yaml
kubectl create -f https://github.com/kubernetes-csi/csi-driver-image-populator/raw/master/deploy/kubernetes-1.16/csi-image-daemonset.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面这个 Pod 会运行一个 Nginx，并从 &lt;code&gt;kfox1111/misc:test&lt;/code&gt; 镜像中获取数据提供服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl create -f - &amp;lt;&amp;lt;EOF
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.13-alpine
    ports:
    - containerPort: 80
    volumeMounts:
    - name: data
      mountPath: /usr/share/nginx/html
  volumes:
  - name: data
    csi:
      driver: image.csi.k8s.io
      volumeAttributes:
          image: kfox1111/misc:test
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;测试一下读取数据：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl exec nginx -- cat /usr/share/nginx/html/test
testing
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;cert-manager-csi&#34;&gt;cert-manager-csi&lt;/h3&gt;

&lt;p&gt;这个驱动和 &lt;a href=&#34;https://github.com/jetstack/cert-manager&#34; target=&#34;_blank&#34;&gt;cert-manager&lt;/a&gt; 协同工作，其目的是无缝地为 Pod 完成证书的请求和加载。这对于 mTLS 或者其它需要使用可信、有效证书的 Pod 间安全连接的工作是很有意义的。这个项目还在实验之中。&lt;/p&gt;

&lt;h2 id=&#34;下一步&#34;&gt;下一步&lt;/h2&gt;

&lt;p&gt;提出这个功能的原因之一就是，Kubernetes 把一个 Pod 调度到节点上时，对节点的存储情况是无知的。Pod 被调度之后，CSI 必须在该节点上创建卷。如果失败，Pod 无法启动，这个过程会一直持续到存储卷可用。&lt;a href=&#34;https://github.com/kubernetes/enhancements/pull/1353&#34; target=&#34;_blank&#34;&gt;存储能力跟踪的 KEP&lt;/a&gt; 是一个解决问题的尝试。&lt;/p&gt;

&lt;p&gt;另外还有一个相关的 &lt;a href=&#34;https://github.com/kubernetes/enhancements/pull/1409&#34; target=&#34;_blank&#34;&gt;用于标准化容量参数的 KEP&lt;/a&gt;。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;CSI Ephemeral Inline Volumes：&lt;code&gt;https://kubernetes.io/blog/2020/01/21/csi-ephemeral-inline-volumes/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Patrick Ohly：&lt;code&gt;https://github.com/pohly&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;后绑定模式：&lt;code&gt;https://kubernetes.io/docs/concepts/storage/storage-classes/#volume-binding-mode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;CSIInlineVolume 特性开关：&lt;code&gt;https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;PMEM-CSI：&lt;code&gt;https://github.com/intel/pmem-csi&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CSIDriver&lt;/code&gt;：&lt;code&gt;https://kubernetes-csi.github.io/docs/csi-driver-object.html#what-fields-does-the-csidriver-object-have&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;加载时 Pod 信息：&lt;code&gt;https://kubernetes-csi.github.io/docs/pod-info.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Kubernetes CSI 文档：&lt;code&gt;https://kubernetes-csi.github.io/docs/ephemeral-local-volumes.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;原始设计文档：&lt;code&gt;https://github.com/kubernetes/enhancements/blob/master/keps/sig-storage/20190122-csi-inline-volumes.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;v0.6.0：&lt;code&gt;https://github.com/intel/pmem-csi/releases/tag/v0.6.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GCE 的特定类型服务器：&lt;code&gt;https://github.com/intel/pmem-csi/blob/v0.6.0/examples/gce.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;QEMU 方式已经集成到了 Makefile：&lt;code&gt;https://github.com/intel/pmem-csi/tree/v0.6.0#qemu-and-kubernetes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;cert-manager：&lt;code&gt;https://github.com/jetstack/cert-manager&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;存储能力跟踪的 KEP：&lt;code&gt;https://github.com/kubernetes/enhancements/pull/1353&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用于标准化容量参数的 KEP：&lt;code&gt;https://github.com/kubernetes/enhancements/pull/1409&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 权威指南第二章校对（2）</title>
      <link>/post/read-k8s-guide-2-2.md/</link>
      <pubDate>Sat, 18 Jan 2020 00:06:57 +0800</pubDate>
      <guid>/post/read-k8s-guide-2-2.md/</guid>
      <description>

&lt;h2 id=&#34;校对的错误&#34;&gt;校对的错误&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;san 同学不经意的一扫，就看到了上一篇中的两个错误：&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Kubeadm 文档中虽然没提到对 CPU 的检测，实际上单核虚拟机运行是会被 preflight 拒绝的。&lt;/li&gt;
&lt;li&gt;preflight 步骤中的：&lt;code&gt;sockert&lt;/code&gt; 应为 &lt;code&gt;socket&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;书接前回&#34;&gt;书接前回&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;近期俗务缠身，第二章的二进制部分又有较多需要更新的内容，因此拖延的比较厉害，见谅见谅。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;二进制部署这部分和现状的主要差别是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;https 已经是标配，而书中用分离的方式来讲述证书部分，显得强调不足。&lt;/li&gt;
&lt;li&gt;Kubernetes 的二进制文件下载方式发生了一些变化。&lt;/li&gt;
&lt;li&gt;etcd 的配置和验证方法也要更新。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;关于-ca-证书&#34;&gt;关于 ca 证书&lt;/h2&gt;

&lt;p&gt;出于安全方面的考虑，Kubernetes 各组件之间的通信都要求使用 https 通信来完成，这就要求我们要为参与通信的各种组件提供证书来支持 https 通信。一般来说，因为都是内部通信，会采用自签署的根证书来签发其它所有证书。统一的根证书有利于建立信任关系，操作也更加方便，因此这里使用单一 CA 的方案。&lt;/p&gt;

&lt;p&gt;生成自签署根证书很容易：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# openssl genrsa -out ca.key 2048
Generating RSA private key, 2048 bit long modulus
...+++
...........................................................................................................................
.....................................+++
e is 65537 (0x10001)
# openssl req -subj &amp;quot;/CN=Kubernetes CA&amp;quot; -new -x509 -days 3650 -key ca.key -out ca.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里需要注意的是 &lt;code&gt;-days&lt;/code&gt; 参数，这个参数代表的是 ca 的有效期，后续的内容中也会看到这个参数，建议读者认真对待这个参数，防止后面的使用过程中，因为证书失效造成不必要的损失。把新生成的证书和密钥保存到 &lt;code&gt;/etc/kubernetes/pki/&lt;/code&gt;，后面我们将会使用这个 ca 签署其它的证书。自签发的 ca 证书应该加入到集群中所有节点的信任列表之中，以保证该 ca 签发的证书能够得到所有节点的信任。例如在 CentOS 7 中需要使用如下命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# cp ca.crt /etc/pki/ca-trust/source/anchors/
# update-ca-trust
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;etcd服务&#34;&gt;etcd服务&lt;/h2&gt;

&lt;p&gt;etcd 是 Kubernetes 集群的主数据库，需要在安装 Kubernetes 各服务之前完成安装和启动。&lt;/p&gt;

&lt;p&gt;从&lt;a href=&#34;https://github.com/coreos/etcd/releases&#34; target=&#34;_blank&#34;&gt;官方 GitHub&lt;/a&gt; 可以找到 etcd 的发行包，下载解压之后，将 etcd 和 etcdctl 文件复制到 &lt;code&gt;/usr/bin&lt;/code&gt;目录。&lt;/p&gt;

&lt;p&gt;为 etcd 编写 systemd 服务配置文件（&lt;code&gt;/usr/lib/systemd/system/etcd.service&lt;/code&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[Unit]
Description=Etcd Server
After=network.target
[Service]
Type=notify
ExecStart=/usr/bin/etcd \
  --data-dir=/var/lib/etcd \
  --client-cert-auth=false \
  --cert-file=/etc/kubernetes/pki/etcd-server.crt \
  --key-file=/etc/kubernetes/pki/etcd-server.key \
  --trusted-ca-file=/etc/kubernetes/pki/ca.crt \
  --listen-client-urls=https://127.0.0.1:2379,https://10.211.55.33:2379 \
  --advertise-client-urls=https://10.211.55.33:2379 \
  --name=kubguide1
Restart=always
RestartSec=10s
LimitNOFILE=40000
[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;--data-dir&lt;/code&gt; 参数指定了 etcd 的数据存储路径。在实际环境中需要注意：etcd 承担了整个集群的核心存储工作，因此对所在磁盘的性能是有较高需求的。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--listen-client-urls&lt;/code&gt; 定义了 etcd 服务器的监听地址。
&lt;code&gt;--cert-file&lt;/code&gt;、&lt;code&gt;--key-file&lt;/code&gt; 以及 &lt;code&gt;--trusted-ca-file&lt;/code&gt; 三个参数的组合形成了一个 ca 到证书的信任链：不论是 etcd 自身还是和 etcd 进行通信的kube-apiserver，都强烈建议使用 https 进行通信，因此上面的命令行中设置了一组证书。&lt;/p&gt;

&lt;p&gt;在启动之前，要使用前面的 ca 文件签发一个 etcd 服务器的证书。&lt;/p&gt;

&lt;p&gt;为证书编写一个配置文件 &lt;code&gt;etcd-server.cnf&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
IP.1 = 10.211.55.33
IP.2 = 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;文件中的 DNS 和 IP 字段应该覆盖 etcd 服务器的所有监听地址。&lt;/p&gt;

&lt;p&gt;生成证书密钥：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# openssl genrsa -out etcd-server.key 2048
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;生成签发请求：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# openssl req -new -key etcd-server.key -subj &amp;quot;/CN=etcd-server&amp;quot; \
    -config etcd-server.cnf -out etcd-server.csr
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;签发证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# openssl x509 -req -in etcd-server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
    -out etcd-server.crt -days 365 -extensions v3_req -extfile etcd-server.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;完成证书生成步骤之后，把 &lt;code&gt;*.key&lt;/code&gt; 和 &lt;code&gt;*.crt&lt;/code&gt; 文件保存到 &lt;code&gt;/etc/kubernetes/pki&lt;/code&gt; 目录中，就可以通过&lt;code&gt;systemctl start&lt;/code&gt;命令启动 etcd 服务了。同时，使用 &lt;code&gt;systemctl enable&lt;/code&gt; 命令将服务加入开机启动列表中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# systemctl daemon-reload
# systemctl enable etcd.service
# systemctl start etcd.service
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;通过执行 &lt;code&gt;etcdctl cluster-health&lt;/code&gt;，可以验证 etcd 是否正确启动：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# etcdctl --endpoints https://127.0.0.1:2379 cluster-health
member 8e9e05c52164694d is healthy: got healthy result from https://10.211.55.33:2379
cluster is healthy
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 权威指南第二章校对（1）</title>
      <link>/post/read-k8s-guide-2-1.md/</link>
      <pubDate>Mon, 06 Jan 2020 23:58:31 +0800</pubDate>
      <guid>/post/read-k8s-guide-2-1.md/</guid>
      <description>

&lt;p&gt;权威指南第二章的内容，是 Kubernetes 有史以来最经久不衰的入门话题：安装。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这里出了个巧合，编写第二章时，我负责的是 kubeadm 的部分，搭建虚拟机集群准备开始校对的时候，发现这一批虚拟机被分配的 IP 和写稿时候是一样的——10.211.55.30-32&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;推荐软硬件规格&#34;&gt;推荐软硬件规格&lt;/h2&gt;

&lt;p&gt;实际上我经常用一组 2G 内存的虚拟机安装测试集群，到处翻看了一下，似乎也没看到很确切的说明，&lt;a href=&#34;https://kubernetes.io/docs/setup/best-practices/cluster-large/&#34; target=&#34;_blank&#34;&gt;Building large clusters&lt;/a&gt; 一文给出了一些配置推荐，其中 1-5 节点的微型集群推荐的 Master 节点配置是 1 核 3.75 G 的虚拟机实例。&lt;/p&gt;

&lt;p&gt;其次是 Docker 版本的推荐，建议去 Release Notes 中查找对应的版本说明，例如 1.14 版本的验证列表包含： 1.13.1, 17.03, 17.06, 17.09, 18.06, 18.09。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;过期文档查看方法示例：
&lt;code&gt;https://kubernetes.io/docs/setup/release/notes/&lt;/code&gt; =&amp;gt;
&lt;code&gt;https://v1-14.docs.kubernetes.io/docs/setup/release/notes/&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;kubeadm-init&#34;&gt;kubeadm init&lt;/h2&gt;

&lt;p&gt;在 &lt;code&gt;kubeadm init&lt;/code&gt; 命令配置一节，原本使用了我自己的 Docker hub 仓库，现在 AWS 提供了 gcr 镜像，这里也就顺便改成了：&lt;code&gt;imageRepository: gcr.azk8s.cn/google_containers&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;init&lt;/code&gt; 命令执行过程中，会进行预检，&lt;a href=&#34;https://kubernetes.io/docs/reference/setup-tools/kubeadm/implementation-details/&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;中也介绍了预检内容：&lt;/p&gt;

&lt;p&gt;init命令首先会执行一段称为preflight的预检工作，对当前的服务器状况进行检查，会导致检查不通过的情况包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubnernetes 需求：

&lt;ul&gt;
&lt;li&gt;操作系统是否为 Linux；&lt;/li&gt;
&lt;li&gt;内核是否为 3.10+ 或者 4+，并启用特定开关；&lt;/li&gt;
&lt;li&gt;是否有合适的 cgroup 子系统配置；&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Docker 服务不存在或者无法工作；&lt;/li&gt;
&lt;li&gt;crictl sockert 无应答。&lt;/li&gt;
&lt;li&gt;当前用户不是 root；&lt;/li&gt;
&lt;li&gt;主机名称不是有效的 DNS 子域；&lt;/li&gt;
&lt;li&gt;kubelet 版本低于 kubeadm 的要求（通常能够容忍一个小版本号的差异）；&lt;/li&gt;
&lt;li&gt;kubelet 版本比 kubeadm 版本高出一个小版本号以上；&lt;/li&gt;
&lt;li&gt;API Server 的绑定端口（10250、10251以及10252）被占用；&lt;/li&gt;
&lt;li&gt;/etc/kubernetes/manifest 已经存在并且非空；&lt;/li&gt;
&lt;li&gt;/proc/sys/net/bridge/bridge-nf-call-iptables 不存在或者内容中不包含“1”；&lt;/li&gt;
&lt;li&gt;公布地址为 ipv6 并且 /proc/sys/net/bridge/bridge-nf-call-ip6tables 文件不存在或者内容中不包含“1”；&lt;/li&gt;
&lt;li&gt;找不到 ip、iptables、mount 或者 nsenter 命令；&lt;/li&gt;
&lt;li&gt;启用了交换文件；&lt;/li&gt;
&lt;li&gt;如果指定了外部 etcd：

&lt;ul&gt;
&lt;li&gt;如果 etcd 版本低于 3.0.14；&lt;/li&gt;
&lt;li&gt;如果 etcd 中引用了不存在的证书或密钥。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;如果没有指定外部的 etcd：

&lt;ul&gt;
&lt;li&gt;如果 2379 端口被占用；&lt;/li&gt;
&lt;li&gt;如果 Etcd.DataDir 路径已经存在并且非空；&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;如果认证模式为 ABAC：

&lt;ul&gt;
&lt;li&gt;abac_policy.json 文件不存在。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;如果认证模式为 WebHook：

&lt;ul&gt;
&lt;li&gt;webhook_authz.conf 不存在。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;常见的三个操作&#34;&gt;常见的三个操作&lt;/h2&gt;

&lt;p&gt;以CentOS 7.5 Minimal版本为例，一般会有三个需要修改的地方：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;关闭交换文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;swapoff -a
删除 /etc/fstab/ 中的 swap 一行，重新启动。
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使用 sysctl 设置net.bridge.bridge-nf-call-iptables 和 net.ipv4.ip_forward 为 1。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;设置 Docker 配置文件 /etc/docker/daemon.json 内容如下，设置 Docker的 cgroupdriver 为 systemd，并重新启动 Docker 服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
&amp;quot;exec-opts&amp;quot;: [&amp;quot;native.cgroupdriver=systemd&amp;quot;],
&amp;quot;log-driver&amp;quot;: &amp;quot;json-file&amp;quot;,
&amp;quot;log-opts&amp;quot;: {
&amp;quot;max-size&amp;quot;: &amp;quot;100m&amp;quot;
},
&amp;quot;storage-driver&amp;quot;: &amp;quot;overlay2&amp;quot;,
&amp;quot;storage-opts&amp;quot;: [
&amp;quot;overlay2.override_kernel_check=true&amp;quot;
]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;单节点集群&#34;&gt;单节点集群&lt;/h2&gt;

&lt;p&gt;该操作并非删除标签，而是去污点操作。&lt;/p&gt;

&lt;h2 id=&#34;参考连接&#34;&gt;参考连接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;预检：&lt;code&gt;https://kubernetes.io/docs/reference/setup-tools/kubeadm/implementation-details/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Building large clusters：&lt;code&gt;https://kubernetes.io/docs/setup/best-practices/cluster-large/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>用 k8up 把 PVC 备份到 S3</title>
      <link>/post/backup-pvc-2-s3/</link>
      <pubDate>Sat, 04 Jan 2020 22:04:14 +0800</pubDate>
      <guid>/post/backup-pvc-2-s3/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://k8up.io/&#34; target=&#34;_blank&#34;&gt;k8up&lt;/a&gt; 是一个基于 &lt;a href=&#34;https://github.com/restic/restic&#34; target=&#34;_blank&#34;&gt;Restic&lt;/a&gt; 的备份工具，可以一次性的或者周期性的把指定的 PVC 备份到 S3 协议的对象存储上去，备份内容还可以使用 Restic 恢复到 S3 或者 PVC 上。除了 PVC，后续还可以用命令的方式，例如 &lt;code&gt;mysqldump&lt;/code&gt;，把数据库等内容备份出来。&lt;/p&gt;

&lt;h2 id=&#34;安装和初始化&#34;&gt;安装和初始化&lt;/h2&gt;

&lt;p&gt;Helm 安装即可：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;helm repo add appuio https://charts.appuio.ch
helm repo update
helm install appuio/k8up
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;安装过程会生成一系列的 CRD，会在后续步骤中使用。&lt;/p&gt;

&lt;p&gt;后续过程中需要两个 Secret，分别用来加密备份和访问 S3：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl create secret generic s3secret --from-literal token=[hidden] \
    --from-literal key=[hidden]
secret/s3secret created
$ kubectl create secret generic backup --from-literal password=PassW0rd
secret/backup created
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;备份-pvc&#34;&gt;备份 PVC&lt;/h2&gt;

&lt;p&gt;k8up 会选择命名空间中注解为 &lt;code&gt;appuio.ch/backup: &amp;quot;true&amp;quot;&lt;/code&gt; 的 PVC 进行备份，我们用下文的工作负载生成两个 PVC，运行起来之后，两个 PVC 分别挂载到容器的 &lt;code&gt;/data1&lt;/code&gt; 和 &lt;code&gt;/data2&lt;/code&gt; 目录中，可以登录到 Pod，在其中生成文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl exec -it debugger-7b8f654484-hrcg9 bash
bash-4.4# echo &amp;quot;Hello world&amp;quot; &amp;gt; /data/data.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建一次性任务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: backup.appuio.ch/v1alpha1
kind: Backup
metadata:
  name: backup-now
spec:
  keepJobs: 4
  backend:
    repoPasswordSecretRef:
      name: backup
      key: password
    s3:
      endpoint: https://s3.amazonaws.com
      bucket: dustise
      accessKeyIDSecretRef:
        name: s3secret
        key: token
      secretAccessKeySecretRef:
        name: s3secret
        key: key
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看 Pod 日志：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl logs -f k8up-1578112449-84d7d4d6cc-q6qsh
2020/01/04 14:30:10 [INFO] New backup job received backup-now in namespace default
2020/01/04 14:30:10 [INFO] Listing all PVCs with annotation appuio.ch/backup in namespace default
2020/01/04 14:30:10 [INFO] Adding data to list
...
2020/01/04 14:30:29 [INFO] default/backupjob-1578148210 is running
2020/01/04 14:30:37 [INFO] default/backupjob-1578148210 finished successfully
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;两个卷的备份均已完成，查看 S3 的情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ s3cmd la
                       DIR   s3://dustise/data/
                       DIR   s3://dustise/index/
                       DIR   s3://dustise/keys/
                       DIR   s3://dustise/snapshots/
2020-01-04 14:30       155   s3://dustise/config
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;发现已经初始化了一个备份结构。&lt;/p&gt;

&lt;h2 id=&#34;还原&#34;&gt;还原&lt;/h2&gt;

&lt;p&gt;备份成功之后，我们希望还原一下，看看备份的内容。&lt;/p&gt;

&lt;p&gt;新建一个 PVC 用作还原目标：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: restore
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建一个还原命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: backup.appuio.ch/v1alpha1
kind: Restore
metadata:
  name: restore2pvc
  namespace: default
spec:
  backend:
    repoPasswordSecretRef:
      key: password
      name: backup
    s3:
      accessKeyIDSecretRef:
        key: token
        name: s3secret
      bucket: dustise
      endpoint: https://s3.amazonaws.com
      secretAccessKeySecretRef:
        key: key
        name: s3secret
  restoreMethod:
    folder:
      claimName: restore
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看运行日志：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl logs -f k8up-1578112449-84d7d4d6cc-q6qsh
2020/01/04 14:43:45 [INFO] Received restore job restore-now in namespace default
2020/01/04 14:43:45 [INFO] default/restorejob-1578149025 is running
2020/01/04 14:43:45 [INFO] default/restorejob-1578149025 is running
2020/01/04 14:43:59 [INFO] default/restorejob-1578149025 is running
2020/01/04 14:44:15 [INFO] default/restorejob-1578149025 finished successfully
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看该卷内容，会发现其中有一个 &lt;code&gt;data/data&lt;/code&gt; 目录，包含了我们的备份内容。&lt;/p&gt;

&lt;h2 id=&#34;附录&#34;&gt;附录&lt;/h2&gt;

&lt;h3 id=&#34;连接&#34;&gt;连接&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;https://k8up.io/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;https://github.com/restic/restic&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;源码&#34;&gt;源码&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: debugger
  name: debugger
spec:
  replicas: 1
  selector:
    matchLabels:
      app: debugger
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: debugger
    spec:
      containers:
      - image: dustise/sleep
        name: sleep
        resources: {}
        volumeMounts:
        - name: data
          mountPath: /data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: data
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: data
  annotations:
    appuio.ch/backup: &amp;quot;true&amp;quot;
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>应用未迁，资源先行</title>
      <link>/post/tips-on-moving-apps-to-k8s/</link>
      <pubDate>Sat, 21 Dec 2019 22:47:13 +0800</pubDate>
      <guid>/post/tips-on-moving-apps-to-k8s/</guid>
      <description>

&lt;h2 id=&#34;引子&#34;&gt;引子&lt;/h2&gt;

&lt;p&gt;在企业服务和云原生的夹缝里厮混了这些年，见到了很多成功或不成功的 K8s 迁移案例。企业在向 Kubernetes 靠拢的过程中，一直有几个跟资源相关的尴尬问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;单个大集群，还是多个小集群？&lt;/li&gt;
&lt;li&gt;少量大节点，还是大量小节点？&lt;/li&gt;
&lt;li&gt;应用的资源如何配合 K8s 的策略进行分配？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些问题有很多模棱两可的相关素材，在任何一个迁移过程中提起这些问题，都能引发大规模的磨洋工事件。然而对于我一直关注的“XX 管理系统”之类的应用来说，这就不是一个大问题了——随大流的应用，选择一个随大流的方向，大概是比较合适的，而 CNCF 红红火火恍恍惚惚，自然不会缺乏数据了。本着这个思路，就诞生了这一篇没什么技术含量的文章。&lt;/p&gt;

&lt;h2 id=&#34;集群规模&#34;&gt;集群规模&lt;/h2&gt;

&lt;p&gt;早在 Kubernetes 1.2 时候，就已经宣布达到 1000 节点的规模了，在 1.6 版本更达到了 5000 节点的规模。各大厂也都有了各自的超大规模单一集群。然而普罗大众的情况是如何呢？ 在 &lt;a href=&#34;https://sysdig.com/blog/sysdig-2019-container-usage-report/&#34; target=&#34;_blank&#34;&gt;Sysdig 2019 年度容器应用报告&lt;/a&gt;中得到的结果是，大于 50 节点规模的集群不足 10%，另外一个佐证是 &lt;a href=&#34;https://medium.com/magalix/how-kubernetes-community-manage-kubernetes-scale-survey-results-1171df1e90fb&#34; target=&#34;_blank&#34;&gt;Mohamed Ahmed 的一篇调查报告&lt;/a&gt;中也提供了类似的数据。这种情况的一种解释是，目前的应用阶段还比较早期，处于试探期间；然而从一个侧面来说，Sysdig 的调研对象针对的是生产应用，也就是说处于生产应用状态下的集群，绝大多数都是这种小规模集群。根据对 CNCF Landscape 中 Distribution 分类的产品的抽查，也可以看到随处可见的 Kubernetes As Service 类似功能的实现，这也证实了小集群协作方案的落地趋势。相对于少量大集群，多个小集群的差异在于：&lt;/p&gt;

&lt;h3 id=&#34;隔离程度高&#34;&gt;隔离程度高&lt;/h3&gt;

&lt;p&gt;虽然现在存在不少沙箱容器实现，然而最易用的、生态最为成熟的方案还是 Docker 为代表的传统容器方案，传统容器方案所缺失的隔离能力，通过多租户多集群方式是一个非常自然的思路。&lt;/p&gt;

&lt;h3 id=&#34;实现难度低&#34;&gt;实现难度低&lt;/h3&gt;

&lt;p&gt;国内几个大厂都有自己的大规模 Kubernetes 集群实现方式，然而通常需要对基础组件大动干戈，甚至不惜使用无法回流社区的孤岛版本，虽然部分大企业的研究院等相关部门已经具备了非常强的研发实力，然而对于通常的 To B 场景来说，这并不是一个合适的选择。&lt;/p&gt;

&lt;h3 id=&#34;运管成本高&#34;&gt;运管成本高&lt;/h3&gt;

&lt;p&gt;多个集群很明显会需要更多的运维和管理人力的投入。&lt;/p&gt;

&lt;h3 id=&#34;资源利用率低&#34;&gt;资源利用率低&lt;/h3&gt;

&lt;p&gt;多个集群都会有自己的 Master 组件、ETCD 集群、网络组件等，这些都会抢占更多原本属于工作负载的系统资源，客观上降低了资源的总体利用率。&lt;/p&gt;

&lt;h2 id=&#34;节点&#34;&gt;节点&lt;/h2&gt;

&lt;p&gt;目前很多 Kubernetes 系统都会使用虚拟机来做为节点。那么虚拟机的资源是多分还是少分呢？下表是一个简单的对比：&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;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;多&lt;/td&gt;
&lt;td&gt;同样的资源总量情况下，相对来说小资源节点会得到更多的数量。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;运维成本&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;通常情况下，节点的运维成本是和节点数量正相关的。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;容错能力&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;较大的节点上通常会集中较多的应用，因此在节点出现故障时，可能会带来更大的损失。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;资源粒度&lt;/td&gt;
&lt;td&gt;大&lt;/td&gt;
&lt;td&gt;小&lt;/td&gt;
&lt;td&gt;单节点资源较大，因此其资源粒度也较大。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;应用副本数&lt;/td&gt;
&lt;td&gt;少&lt;/td&gt;
&lt;td&gt;多&lt;/td&gt;
&lt;td&gt;同一应用的多个副本，如果调度到同一个节点上的话，对于提高其负载能力和健壮性来说并无裨益。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;副本规模&lt;/td&gt;
&lt;td&gt;大&lt;/td&gt;
&lt;td&gt;小&lt;/td&gt;
&lt;td&gt;毫无疑问，具备更多资源的大节点，能够运行更大资源需求范围的容器应用。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;系统开销&lt;/td&gt;
&lt;td&gt;少&lt;/td&gt;
&lt;td&gt;多&lt;/td&gt;
&lt;td&gt;每个虚拟机都会有自己的操作系统、网络等基础开销，因此相对于少量大节点来说，大量的小节点会消耗更多的资源。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;除了这些原则性的条目之外，更重要的决策依据就是运行在集群上的应用需求。例如某租户的集群需要支撑 20 个应用，共300 个 Pod，按照常见的每节点 30-50 Pod 的分布，就需要 6-10 个运算节点（Node）。以 10 节点算，加入系统保留、冗余等计算，可能需要 10 * 120G 的虚拟机实例；然而考虑到故障情况——一个节点的故障，最好的结果也是短期内降低 10% 的算力。如果扩张到 40 个 32G 的虚拟机节点，会大幅降低单节点故障的影响——当然也会提高网络的复杂性和效率要求。&lt;/p&gt;

&lt;h2 id=&#34;应用资源&#34;&gt;应用资源&lt;/h2&gt;

&lt;p&gt;Java 应用是特别常见的迁移案例，除掉微服务化、网格、分布式等改造要求之外，资源的申请和限制是一个必须要面对的门槛。&lt;code&gt;requests&lt;/code&gt; 是个用于调度的定义，Kubernetes 根据这个要求来选择能够满足要求的节点来分配应用。而 &lt;code&gt;limits&lt;/code&gt; 则会用于触发 OOM。&lt;/p&gt;

&lt;p&gt;众所周知的是，Java 的早期版本是无法识别容器内的内存限制的，因此如果没有限制堆内存上限，又开启了 &lt;code&gt;limits&lt;/code&gt;，就会被 Kubernetes 杀掉。因此针对容器中运行的情况，需要进行一些启动参数的设置。&lt;/p&gt;

&lt;p&gt;如果允许更新到新版本的 JVM，可以使用新引入的 &lt;code&gt;UseCGroupMemoryLimitForHeap&lt;/code&gt;、&lt;code&gt;MaxRAMFraction&lt;/code&gt; 参数，让 JVM 直接继承容器的定义。&lt;/p&gt;

&lt;p&gt;如果无法直接升级，那么就有必要设置 xmx 和 xms 参数了，这里有几个小建议：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;xmx 和 xms，request 和 limits 建议设成一致，能省掉很多麻烦。&lt;/li&gt;
&lt;li&gt;tmpfs、filemapping 等都是可能的内存大户。&lt;/li&gt;
&lt;li&gt;JVM 并不是唯一的内存消耗者，一般建议 Limit 大于 XMX 25% 以上。&lt;/li&gt;
&lt;li&gt;/sys/fs/cgroup/memory/memory.stat 是你的好朋友。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Rudr 初体验</title>
      <link>/post/rudr-at-a-glance/</link>
      <pubDate>Wed, 04 Dec 2019 21:20:35 +0800</pubDate>
      <guid>/post/rudr-at-a-glance/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://github.com/oam-dev/spec&#34; target=&#34;_blank&#34;&gt;OAM（开放应用模型）&lt;/a&gt; 是一次对应用运行及其支撑环境进行抽象的有意思的尝试，与之对应的控制器 &lt;a href=&#34;https://github.com/oam-dev/rudr&#34; target=&#34;_blank&#34;&gt;Rudr&lt;/a&gt; 也在同一时间诞生。有了 Rudr，OAM 就不是一个简单的标准，而是一个可以尝试落地的原型了。官方仓库提供了很好的&lt;a href=&#34;https://github.com/oam-dev/rudr/blob/master/docs/tutorials/deploy_and_update.md&#34; target=&#34;_blank&#34;&gt;入门文档&lt;/a&gt;，借此文档的帮助，能够很好的理解规范中莫名其妙的概念。这里就按照官方教程走一通，看看这种方法让应用部署运行过程发生了什么变化。&lt;/p&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;Rudr 需要 Kubernetes 1.15 以上的版本，并且使用 Helm 3 进行安装。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ git clone https://github.com/oam-dev/rudr.git
正克隆到 &#39;rudr&#39;...
remote: Enumerating objects: 49, done.
...
$ cd rudr
...
$ helm install rudr charts/rudr
...
NOTES:
Rudr is a Kubernetes controller to manage Configuration CRDs.

It has been successfully installed.
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;非常谦虚的一个 Note。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;部署一个-component&#34;&gt;部署一个 Component&lt;/h2&gt;

&lt;p&gt;Component 是 OAM 中的一个运行单位，代表一种运行负载，其类型可能有 Server、Job 等。下面使用示例代码创建一个 Component 对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f examples/helloworld-python-component.yaml
componentschematic.core.oam.dev/helloworld-python-v1 created
$ kubectl get component
NAME                   AGE
helloworld-python-v1   35s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看这个源文件，其中声明了一个 &lt;code&gt;Server&lt;/code&gt; 类型的组件，用参数的方式定义了两个环境变量 &lt;code&gt;TARGET&lt;/code&gt; 和 &lt;code&gt;PORT&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;查看-traits&#34;&gt;查看 Traits&lt;/h2&gt;

&lt;p&gt;接下来看看 Kubernetes + Rudr 为应用提供了哪些运行支撑能力：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get traits
NAME             AGE
autoscaler       13m
empty            13m
ingress          13m
manual-scaler    13m
volume-mounter   13m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;熟悉 Kubernetes 的同学应该看得出，除了奇怪的 &lt;code&gt;empty&lt;/code&gt;，其他都是常见的部署元素。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get traits autoscaler -o yaml
apiVersion: core.oam.dev/v1alpha1
kind: Trait
...
spec:
  appliesTo:
  - core.oam.dev/v1alpha1.Server
  - core.oam.dev/v1alpha1.Task
  properties: |
    {
      &amp;quot;$schema&amp;quot;: &amp;quot;http://json-schema.org/draft-
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里可以看到，HPA 适用于 Server 和 Task 两种组件，定义了最大实例数、最小实例数以及 CPU/内存消耗阈值。&lt;/p&gt;

&lt;h2 id=&#34;运行应用&#34;&gt;运行应用&lt;/h2&gt;

&lt;p&gt;有了 Component 和 Trait，接下来可以用 &lt;code&gt;Configuration&lt;/code&gt; 启动应用了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f examples/first-app-config.yaml
applicationconfiguration.core.oam.dev/first-app created
$ kubectl get pods
NAME                                              READY   STATUS    RESTARTS   AGE
first-app-helloworld-python-v1-855479556f-6qvk8   1/1     Running   0          38s
...
$ kubectl get ingress
NAME                                           HOSTS         ADDRESS   PORTS   AGE
first-app-helloworld-python-v1-trait-ingress   example.com             80      12m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 已经启动，Ingress 对象也已经建立起来，可以看看他的运行结果：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ export POD_NAME=$(kubectl get pods -l &amp;quot;oam.dev/instance-name=first-app-helloworld-python-v1,app.kubernetes.io/name=first-app&amp;quot; -o jsonpath=&amp;quot;{.items[0].metadata.name}&amp;quot;)
...
$ kubectl port-forward $POD_NAME 9999:9999 &amp;amp;
Forwarding from [::1]:9999 -&amp;gt; 9999
$ curl http://127.0.0.1:9999
Hello Rudr!
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;修改配置&#34;&gt;修改配置&lt;/h2&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl edit&lt;/code&gt; 修改上一步的配置，把 target 参数修改为 &lt;code&gt;World&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
    parameterValues:
    - name: target
      value: World
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;应用之后，会看到 Pod 被重建，重新执行上面的测试步骤，返回信息变成 &lt;code&gt;Hello World&lt;/code&gt;。&lt;/p&gt;

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

&lt;p&gt;实际上单就这个例子来说，对比入门的 Deployment + Service + Ingress 三件套来说，复杂度并没有什么区别。然而 Component 对象的工作负载类型除了 Server 之外，还有 Job、Serverless 等复杂类型，用 Traits 可以描述多种运维能力，更不要说还有暂未浮出水面的 Application Scope 对象，猜测这个模型在公有云、多云以及混合云下，可能会有相当大的想象空间。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Zalando 是如何管理 140 多个 Kubernetes 集群的</title>
      <link>/post/how-zalando-manages-140-kubernetes-clusters/</link>
      <pubDate>Sat, 02 Nov 2019 23:41:56 +0800</pubDate>
      <guid>/post/how-zalando-manages-140-kubernetes-clusters/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://srcco.de/posts/how-zalando-manages-140-kubernetes-clusters.html&#34; target=&#34;_blank&#34;&gt;HOW ZALANDO MANAGES 140+ KUBERNETES CLUSTERS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://srcco.de/pages/about.html&#34; target=&#34;_blank&#34;&gt;Henning Jacobs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最近我接到一个问题：“你是如何管理这么多 Kubernetes 的？”。本文试图揭示 Zalando 在 AWS 管理 140 多个 Kubernetes 集群的秘密。&lt;/p&gt;

&lt;p&gt;我写过一篇文章：&lt;a href=&#34;https://srcco.de/posts/many-kubernetes-clusters.html&#34; target=&#34;_blank&#34;&gt;为什么需要多集群&lt;/a&gt;，&lt;a href=&#34;https://twitter.com/mikkeloscar&#34; target=&#34;_blank&#34;&gt;Mikkel&lt;/a&gt; 在 KubeCon EU 2018 上做了关于如何在 Kubernetes 基础设施上进行持续交付的精彩分享。这里基本是对现存信息的一个梳理。&lt;/p&gt;

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

&lt;p&gt;Zalando 有 200 多个开发团队，他们全权负责自己的应用，其中也包括 7*24 待命的支持工作。我们的 Kubernetes 平台团队为 1000 多个 Zalando 开发者提供 Kubernetes 即服务的支持工作，工作过程中我们遵循如下准则：&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;/ul&gt;

&lt;h2 id=&#34;架构&#34;&gt;架构&lt;/h2&gt;

&lt;p&gt;我们的集群是成对供应的，例如给每个域或者“产品社区”提供一个生产、一个非生产环境。&lt;/p&gt;

&lt;p&gt;每个集群都是属于一个全新的、隔离的 AWS 账号。我们使用一个自定义的 Python 工具（&lt;a href=&#34;https://github.com/zalando-stups/sevenseconds&#34; target=&#34;_blank&#34;&gt;Seven Seconds&lt;/a&gt;）对 AWS 基础设施进行配置，对 Kubernetes 和非 Kubernetes 账号一视同仁（即将下线的 &lt;code&gt;STUPS&lt;/code&gt; 基础设施）。&lt;/p&gt;

&lt;p&gt;我们整个&lt;a href=&#34;https://github.com/zalando-incubator/kubernetes-on-aws&#34; target=&#34;_blank&#34;&gt;生产环境的配置都保存在 Github 上&lt;/a&gt;。集群使用 CloudFormation（CF）模板。每个集群至少有四个 CF 栈：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ETCD 集群（存在于主节点之外）：&lt;a href=&#34;https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/etcd-cluster.yaml&#34; target=&#34;_blank&#34;&gt;etcd-cluster.yaml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;主集群：&lt;a href=&#34;https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/cluster.yaml&#34; target=&#34;_blank&#34;&gt;cluster.yaml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;主机群节点池：&lt;a href=&#34;https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/node-pools/master-default/stack.yaml&#34; target=&#34;_blank&#34;&gt;master stack.yaml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;缺省的工作节点池：&lt;a href=&#34;https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/node-pools/worker-default/stack.yaml&#34; target=&#34;_blank&#34;&gt;worker stack.yaml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以有多种工作节点池，例如 GPU 节点、EC2 Spot 实例等。&lt;/p&gt;

&lt;p&gt;注意：我们没有使用 Terraform（从来没有）。&lt;/p&gt;

&lt;p&gt;主节点和工作节点都运行在我们的自定义 AMI 上。这个 AMI 是从 Ubuntu 基础上构建出来的，并且包含了 Kubernetes 所需的所有 Docker 镜像。从前我们用过 ContainerLinux，后来还是决定采用更主流的发行版，以保证持续性。这个预制的 AMI 还帮我们减少了启动时间（集群伸缩更快）。&lt;/p&gt;

&lt;h2 id=&#34;配置&#34;&gt;配置&lt;/h2&gt;

&lt;p&gt;所有的集群以及 AWS 账号，都注册在一个中央集群仓库中。集群仓库使用 PostgreSQL 为数据库，提供了一组 REST API。可以在 &lt;a href=&#34;https://github.com/zalando-incubator/cluster-lifecycle-manager/blob/master/docs/cluster-registry.yaml&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt; 上浏览这个 OpenAPI 的规范。每个集群都有如下属性：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;只读的集群 ID，例如 &amp;ldquo;aws:123123123123:us-east-1:kube-9&amp;rdquo;&lt;/li&gt;
&lt;li&gt;集群别名：例如 “foobarlab”&lt;/li&gt;
&lt;li&gt;所在的 AWS 账号(账号 ID 和 Region)&lt;/li&gt;
&lt;li&gt;环境（生产还是测试）&lt;/li&gt;
&lt;li&gt;配置成熟度（稳定、Beta、Alpha 或者 dev）&lt;/li&gt;
&lt;li&gt;生命周期（已供给、已分配或者已销毁）&lt;/li&gt;
&lt;li&gt;集群特定的键值对信息，例如外部 API Key 等。&lt;/li&gt;
&lt;li&gt;已配置好的节点池（也就是 EC2 实例类型）和针对节点池的键值对配置&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们的工具集（&lt;a href=&#34;https://github.com/hjacobs/kube-resource-report/&#34; target=&#34;_blank&#34;&gt;kube-resource-report&lt;/a&gt; 和 &lt;a href=&#34;https://codeberg.org/hjacobs/kube-web-view&#34; target=&#34;_blank&#34;&gt;kube-web-view&lt;/a&gt;）能够查询集群仓库的 REST API，列出所有集群，比如 &lt;code&gt;zkubectl&lt;/code&gt; 命令行工具能够列出集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ zkubectl list
Id                                           │Alias                           │Environment│Channel│Version
aws:123740508747:eu-central-1:kube-1          foobarlab                        production  stable  5f4316c
aws:456818767898:eu-central-1:kube-1          foobarlab-test                   test        beta    9f1b369
aws:789484029646:eu-central-1:kube-1          abckub                           production  stable  5f4316c
aws:012345670034:eu-central-1:kube-1          abckub-test
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;你会看到两对集群（&lt;code&gt;foobarlab&lt;/code&gt; 和 &lt;code&gt;abckub&lt;/code&gt;），生产集群使用的是 &lt;code&gt;stable&lt;/code&gt;，非生产集群则使用 &lt;code&gt;beta&lt;/code&gt; 配置。&lt;code&gt;Version&lt;/code&gt; 列显示的是当前集群配置的 git sha。&lt;/p&gt;

&lt;p&gt;用 &lt;a href=&#34;https://codeberg.org/hjacobs/kube-web-view&#34; target=&#34;_blank&#34;&gt;Kubernetes Web View&lt;/a&gt; 对类似的集群进行渲染：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/kube-web-view-clusters.png&#34; alt=&#34;kube-web-view-clusters.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;还可以参考我的另一篇文章：&lt;a href=&#34;https://srcco.de/posts/kubernetes-web-uis-in-2019.html&#34; target=&#34;_blank&#34;&gt;缺乏多集群支撑案例的 Kubernetes Web UI&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;更新&#34;&gt;更新&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/zalando-incubator/cluster-lifecycle-manager&#34; target=&#34;_blank&#34;&gt;集群生命周期管理器&lt;/a&gt;持续的对集群仓库以及 Git 仓库的变更进行监控。CLM 会在如下时机进行变更：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudFormation 更新&lt;/li&gt;
&lt;li&gt;节点必须进行滚动更新（例如 AMI 发生变化）&lt;/li&gt;
&lt;li&gt;Kubernetes 自身发生了变更（多数时候的表现是 &lt;code&gt;kube-system&lt;/code&gt; 中的 DaemonSet 和 Deployment 的变更）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;images/cluster-lifecycle-manager.png&#34; alt=&#34;Cluster Lifecycle Manager&#34; /&gt;&lt;/p&gt;

&lt;p&gt;通过对 &lt;code&gt;dev&lt;/code&gt; 分支发起 PR 的方式来初始化一个更新动作（例如更新一些系统组件）。每个变更的 PR 都会自动的进行端到端测试。 只有通过测试并且由人手工批准的 PR 才能够合并。&lt;a href=&#34;https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/test/e2e/README.md&#34; target=&#34;_blank&#34;&gt;端到端测试过程&lt;/a&gt;会针对新建的集群运行官方的 Kubernetes 一致性验证，以及 Zalando 自己的测试案例。这个测试的范围包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;集群的创建和更新（端到端测试会用前一个版本创建一个新的集群，并用 PR 内容进行更新）&lt;/li&gt;
&lt;li&gt;Kubernetes 的核心功能：Deployment、StatefulSet 等&lt;/li&gt;
&lt;li&gt;Zalando 的准入控制器逻辑&lt;/li&gt;
&lt;li&gt;审计日志&lt;/li&gt;
&lt;li&gt;Ingress、&lt;a href=&#34;https://github.com/kubernetes-incubator/external-dns&#34; target=&#34;_blank&#34;&gt;外部 DNS&lt;/a&gt;、AWS ALB 以及 &lt;a href=&#34;https://github.com/zalando/skipper&#34; target=&#34;_blank&#34;&gt;Skipper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PodSecurityPolicy&lt;/li&gt;
&lt;li&gt;使用自定义指标进行自动伸缩&lt;/li&gt;
&lt;li&gt;测试 AWS IAM 集成&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;每个 e2e 测试目前需要 35-59 分钟。测试成功的 &lt;a href=&#34;https://github.com/zalando-incubator/kubernetes-on-aws/pull/2601&#34; target=&#34;_blank&#34;&gt;PR&lt;/a&gt;，只需要一个 &lt;code&gt;+1&lt;/code&gt;，就能进行合并：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/kubernetes-on-aws-pr-e2e.png&#34; alt=&#34;PR e2e&#34; /&gt;&lt;/p&gt;

&lt;p&gt;每个变更都会在不同的分支中迁移，一直到进入稳定分支。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/cluster-updates.png&#34; alt=&#34;cluster update&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在滚动更新集群节点以及集群的自动伸缩过程中，我们的基础设施必须对正在预备下线的服务器上运行的 Pod 进行驱逐。可以使用 Pod Disruption Budgets 的声明，来保障平稳的更新过程。我们为更新或类似行为定义了下面的 SLA：&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SLA&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;更新期间强制终结的 Pod 的最小生存期&lt;/td&gt;
&lt;td&gt;3 天&lt;/td&gt;
&lt;td&gt;8 小时&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;在选定节点之后，需要等待多久才开始强行终结 Pod&lt;/td&gt;
&lt;td&gt;6 小时&lt;/td&gt;
&lt;td&gt;2 小时&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;同一个节点上强行终结 Pod 的时间间隔&lt;/td&gt;
&lt;td&gt;5 分钟&lt;/td&gt;
&lt;td&gt;5 分钟&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;同一个 PDB 中将被终止的就绪 Pod 的最小生存期&lt;/td&gt;
&lt;td&gt;1 小时&lt;/td&gt;
&lt;td&gt;1 小时&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;同一个 PDB 中将被终止的未就绪 Pod 的最小生存期&lt;/td&gt;
&lt;td&gt;6 小时&lt;/td&gt;
&lt;td&gt;6 小时&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;因此应用 Pod 会在 3 天之后被强行终止——即使定义了 PDB 的情况。这种行为模式让我们在部分应用配置失常的情况下也能持续更新。&lt;/p&gt;

&lt;p&gt;注意：我们的用户（开发团队）可以在任何时间阻止集群更新（例如发现了问题）。&lt;/p&gt;

&lt;p&gt;请参见 &lt;a href=&#34;https://www.youtube.com/watch?v=1xHmCrd8Qn8&#34; target=&#34;_blank&#34;&gt;Miokkel 的 KubeCon 演讲&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;避免配置发散&#34;&gt;避免配置发散&lt;/h2&gt;

&lt;p&gt;所有的集群看起来都差不多，只有少量配置项目有些不同：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secret：例如外部日志服务的凭据&lt;/li&gt;
&lt;li&gt;节点池以及其中的实例规格&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cluster Autoscaler 能根据资源需要对集群的节点池进行伸缩，无需手工配置节点池的大小。下图是我们一个集群在两天之内的伸缩情况：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/cluster-autoscaling.png&#34; alt=&#34;cluster autoscaling&#34; /&gt;&lt;/p&gt;

&lt;p&gt;有些组件需要根据集群大小进行纵向伸缩。我们使用  Vertical Pod Autoscaler（VPA）来避免对这些值进行手工调节。目前有如下系统组件在使用 VPA：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prometheus&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes-incubator/external-dns&#34; target=&#34;_blank&#34;&gt;外部 DNS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Heapster/Metrics Server&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/zalando-incubator/kube-ingress-aws-controller&#34; target=&#34;_blank&#34;&gt;我们的 ALB Ingres 控制器&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们最小的 Prometheus 实例仅仅使用 512 MB，也有使用 9GB 的大户，例如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/prometheus-memory-vpa.png&#34; alt=&#34;prom memory&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;监控&#34;&gt;监控&lt;/h2&gt;

&lt;p&gt;我们主要的监控系统是 &lt;a href=&#34;https://zmon.io/&#34; target=&#34;_blank&#34;&gt;ZMON&lt;/a&gt;，ZMON 中有个实体的概念，表达的是你要监控的对象——例如服务器、Pod 甚至是团队。&lt;/p&gt;

&lt;p&gt;创建一个新的集群会自动注册新的实体（例如新的 AWS 账号、节点、Deployment、Pod 等）。从而为新的实体启用一些常用检查和告警。&lt;/p&gt;

&lt;p&gt;ZMON 提供了指标、告警以及仪表盘。&lt;/p&gt;

&lt;p&gt;我们的 Kubernetes 团队不会在 Pod 重启时候收到警告。开发团队负责应用的整个生命周期。&lt;/p&gt;

&lt;p&gt;我们还使用 OpenTracing（LightStep）获得跨集群的可观察性，中心化应用日志（Scalyr）、&lt;a href=&#34;https://github.com/hjacobs/kube-resource-report/&#34; target=&#34;_blank&#34;&gt;kube-resource-report&lt;/a&gt; 和 &lt;a href=&#34;https://codeberg.org/hjacobs/kube-web-view&#34; target=&#34;_blank&#34;&gt;kube-web-view&lt;/a&gt;）。&lt;/p&gt;

&lt;h2 id=&#34;魔改-kubernetes&#34;&gt;魔改 Kubernetes&lt;/h2&gt;

&lt;p&gt;我们的配置是否对 Kubernetes 进行了大量魔改呢？答案是：不很多：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes API 认证用了 Zalando Oauth token&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&#34;https://srcco.de/posts/how-zalando-manages-140-kubernetes-clusters.html#id5&#34; target=&#34;_blank&#34;&gt;Kubelet 参数&lt;/a&gt; 禁用 CPU Throlling&lt;/li&gt;
&lt;li&gt;强制 request == limit，防止内存超售&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&#34;https://github.com/kubernetes-incubator/external-dns&#34; target=&#34;_blank&#34;&gt;外部 DNS&lt;/a&gt; &lt;a href=&#34;https://github.com/zalando-incubator/kube-ingress-aws-controller&#34; target=&#34;_blank&#34;&gt;ALB Ingress 控制器&lt;/a&gt;，以及 &lt;a href=&#34;https://github.com/zalando/skipper&#34; target=&#34;_blank&#34;&gt;Skipper&lt;/a&gt;，Ingress 注解是可选的，不过 &lt;a href=&#34;https://opensource.zalando.com/skipper/kubernetes/ingress-usage/&#34; target=&#34;_blank&#34;&gt;Skipper 有些有用的功能&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PlatformCredentialsSet&lt;/code&gt; 是一个用来集成 OAuth 的 CRD&lt;/li&gt;
&lt;li&gt;我们用 &lt;a href=&#34;https://github.com/zalando-incubator/stackset-controller&#34; target=&#34;_blank&#34;&gt;StackSet&lt;/a&gt;来实现流量切换和渐进部署&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/hjacobs/kube-downscaler&#34; target=&#34;_blank&#34;&gt;kube-downscaler&lt;/a&gt; 用于在作业后降级测试部署&lt;/li&gt;
&lt;li&gt;我们的 DNS 配置有些不同：我们使用 &lt;code&gt;ndots: 2&lt;/code&gt; 的配置（&lt;a href=&#34;https://srcco.de/posts/how-zalando-manages-140-kubernetes-clusters.html#id6&#34; target=&#34;_blank&#34;&gt;官方配置是 &lt;code&gt;ndots: 5&lt;/code&gt;&lt;/a&gt;）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;非生产集群提供的是类似 GKE 或者 Digital Ocean 集群类似的普通集群的功能。生产集群有些容器：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;只能通过 CICD 进行 Kubernetes API 的操作&lt;/li&gt;
&lt;li&gt;用 Webhook 执行强制的合规措施，例如使用某些标签，或者允许用于生产的镜像&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;我们的这种做法再过去几年中工作良好，让我们在无需扩张团队的情况下得到了成长：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;我们能够无缝的把我们一个老的 Kubernetes 1.4 在无停服的情况下，升级到 1.14&lt;/li&gt;
&lt;li&gt;我们能够跟进 Kubernetes 的季度发布，也就是说我们在每个季度都可以进行升级&lt;/li&gt;
&lt;li&gt;频繁的集群更新让大家开始接受一个观点：小的中断是正常的（目前的 Pod 最长寿 20 多天）&lt;/li&gt;
&lt;li&gt;我们尝试避免出现宠物集群：集群看起来差不多，VPA 协助我们避免人工调节&lt;/li&gt;
&lt;li&gt;我们的自动端到端测试救了我们不止一次（例如 &lt;a href=&#34;https://twitter.com/try_except_/status/1181602709155323905&#34; target=&#34;_blank&#34;&gt;最近 1.14.7 的 Issue&lt;/a&gt;(&lt;a href=&#34;https://twitter.com/try_except_/status/1181602709155323905)）&#34; target=&#34;_blank&#34;&gt;https://twitter.com/try_except_/status/1181602709155323905)）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要进一步了解这方面的信息，可以看看 &lt;a href=&#34;https://github.com/zalando/public-presentations&#34; target=&#34;_blank&#34;&gt;Zalando 的公开仓库&lt;/a&gt;，还可以在 Twitter 上找到我们的一些团队成员：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://twitter.com/jannis_r&#34; target=&#34;_blank&#34;&gt;Jannis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://twitter.com/arjun_rn&#34; target=&#34;_blank&#34;&gt;Arjun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://twitter.com/backenhoernchen&#34; target=&#34;_blank&#34;&gt;Martin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://twitter.com/mikkeloscar&#34; target=&#34;_blank&#34;&gt;Mikkel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://twitter.com/njuettner&#34; target=&#34;_blank&#34;&gt;Nick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://twitter.com/sszuecs&#34; target=&#34;_blank&#34;&gt;Sandor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;也欢迎和 &lt;a href=&#34;https://twitter.com/ZalandoTech&#34; target=&#34;_blank&#34;&gt;ZalandoTech&lt;/a&gt;、和&lt;a href=&#34;https://twitter.com/try_except_&#34; target=&#34;_blank&#34;&gt;我&lt;/a&gt;打个招呼。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>YAML 程序员眼中的 OAM</title>
      <link>/post/oam-and-lockin/</link>
      <pubDate>Mon, 21 Oct 2019 14:30:23 +0800</pubDate>
      <guid>/post/oam-and-lockin/</guid>
      <description>

&lt;p&gt;在 10.17 ，阿里云和微软联袂发布了一个有意思的新东西：OAM（开放应用模型）。这个项目要解决的问题是：用一致的、定义良好的模型来对应用进行描述。&lt;/p&gt;

&lt;p&gt;Kubernetes 达成了一个小目标：不管是什么云，上面都有 Kubernetes 的一席之地。OAM 的小目标是什么呢？&lt;/p&gt;

&lt;p&gt;OAM 用（Holy）YAML 对应用程序进行了描述，其中核心组件包含了几个：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Component：组件交付物&lt;/li&gt;
&lt;li&gt;Application Scope：部署目标&lt;/li&gt;
&lt;li&gt;Traits：运维能力&lt;/li&gt;
&lt;li&gt;Application Configuration：应用配置&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一头雾水是吧？还好每个对象都提供了代码范例，可以拿来解释。&lt;/p&gt;

&lt;h2 id=&#34;component&#34;&gt;Component&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;一种类似 Pod 的东西。。。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: core.oam.dev/v1alpha1
kind: ComponentSchematic
metadata:
  name: admin-backend
  annotations:
    version: v1.0.0
    description: &amp;gt;
      Sample component schematic that describes the backend for our Twitter bot.
spec:
  workloadType: core.oam.dev/v1.SingletonServer
  osType: linux
  parameters:
  ...
  - name: twitter-access-token-secret
    description: Twitter API access token secret
    type: string
    required: true
  containers:
  - name: my-twitter-bot-backend
    image:
      name: example/my-twitter-bot-backend:1.0.0
      digest: sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b
    resources:
      cpu:
        required: 1.0
      memory:
        required: 100MB
      volumes:
      - name: config
        mountPath: /var/lib/my-twitter-bot/conf
        accessMode: RW
        sharingPolicy: Exclusive
    ports:
    - name: http
      value: 8080
    env:
    ...
    - name: TWITTER_ACCESS_TOKEN_SECRET
      fromParam: &#39;twitter-access-token-secret&#39;
    livenessProbe:
      httpGet:
        port: 8080
        path: /healthz
    readinessProbe:
      httpGet:
        port: 8080
        path: /healthz
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;很像 Kubernetes 有没有？容器、参数、资源（外部加载卷的加载方式，类似 &lt;code&gt;volumeMount&lt;/code&gt; 也定义在资源里）、端口和环境变量都是 YAML 程序员们很熟悉的东西。最值得注意的是 &lt;code&gt;workloadType&lt;/code&gt;，工作负载的类型可以分为核心和扩展两个大类，其中核心工作负载有一个明确要求：所有实现本规范的平台必须支持核心工作负载。&lt;/p&gt;

&lt;p&gt;核心工作负载有几个类型：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server：可多实例运行的，对外提供服务的守护进程。&lt;/li&gt;
&lt;li&gt;Singleton Server：只能单实例运行的，对外提供服务的守护进程。&lt;/li&gt;
&lt;li&gt;Worker：能够多实例运行，不对外提供服务的守护进程。&lt;/li&gt;
&lt;li&gt;Singleton Worker：不对外提供服务，不可复制的守护进程。&lt;/li&gt;
&lt;li&gt;Task：不对外提供服务，可复制，非守护进程（一次性）。&lt;/li&gt;
&lt;li&gt;Singleton Task：不对外提供服务，不可复制，非守护进程（一次性）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外这里还有一个字段叫 &lt;code&gt;ConfigFile&lt;/code&gt;，用于存储配置内容。&lt;/p&gt;

&lt;p&gt;在组件模型一节的尾部，给出了下面这样的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: core.oam.dev/v1alpha1
kind: ComponentSchematic
metadata:
  name: azurefunction
  annotations:
    version: v1.0.0
    description: &amp;quot;Extended workflow example&amp;quot;
spec:
  workloadType: azure.com/v1.Function
  parameters:
  - name: github-token
    description: GitHub API session key
    type: string
    required: true
  workloadSettings:
    - name: source
      value: git://git.example.com/function/myfunction.git
    - name: github_token
      fromParam: github-token
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个例子展示的是扩展类型的组件：从 git 拉取代码，用于提供 Function 服务。&lt;/p&gt;

&lt;h2 id=&#34;trait&#34;&gt;Trait&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;一种运行平台中，针对特定工作负载进行运维支撑的能力，例如下面例子中的手动伸缩，似乎 Service Mesh 也应该名列此列？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: core.oam.dev/v1alpha1
kind: Trait
metadata:
  name: ManualScaler
  annotations:
    version: v1.0.0
    description: &amp;quot;Allow operators to manually scale a workloads that allow multiple replicas.&amp;quot;
spec:
  appliesTo:
    - core.oam.dev/v1alpha1.Server
    - core.oam.dev/v1alpha1.Worker
    - core.oam.dev/v1alpha1.Task
  properties:
    type: object
    properties: |
      {
        &amp;quot;$schema&amp;quot;: &amp;quot;http://json-schema.org/draft-07/schema#&amp;quot;,
        &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
        &amp;quot;required&amp;quot;: [&amp;quot;replicaCount],
        &amp;quot;properties&amp;quot;: {
          &amp;quot;replicaCount&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;the target number of replicas to scale a component to.&amp;quot;,
            &amp;quot;minimum&amp;quot;: 0
          }
        }
      }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里定义了一个用来做手动伸缩的 Trait，它仅适用于第一节中提到的几个可伸缩的工作负载类型。这个 Traits 仅包含一个必要字段，用于设置副本数量。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;但是在 YAML 里面包 JSON 真的好吗？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;application-scopes&#34;&gt;Application Scopes&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;百撕不得其解的一个概念。通过外部设施，如网络或者健康对应用范围进行划分，把应用进行聚合。
并且在 Application Configuration 中作为一个部署目标进行实例化。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: core.oam.dev/v1alpha1
kind: ApplicationScope
metadata:
  name: health
  annotations:
    version: v1.0.0
    description: &amp;quot;aggregated health state for a group of components.&amp;quot;
spec:
  type: core.oam.dev/v1alpha1.HealthScope
  allowComponentOverlap: true
  parameters:
    - name: probe-method
      description: The method to probe the components, e.g. &#39;httpGet&#39;.
      type: string
      required: true
...
    - name: required-healthy-components
      description: Comma-separated list of names of the components required to be healthy for the scope to be health.
      type: []string
      required: false
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;application-configuration&#34;&gt;Application Configuration&lt;/h2&gt;

&lt;p&gt;前面的几个概念中，描述了组件的定义、平台提供的运维能力、以及应用的部署范围，最终应用要运行起来，需要进行一个部署过程，部署过程除了把前面提到的对象组合起来之外，还需要加入一些配置内容。本对象就是用来完成这一功能的。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: core.oam.dev/v1alpha1
kind: ApplicationConfiguration
metadata:
  name: my-vpc-network
spec:
  variables:
    - name: networkName
      value: &amp;quot;my-vpc&amp;quot;
  scopes:
    - name: network
      type: core.oam.dev/v1alpha1.Network
      properties:
        - name: network-id
          value: &amp;quot;[fromVariable(networkName)]&amp;quot;
        - name: subnet-id
          value: &amp;quot;my-subnet&amp;quot;
---
apiVersion: core.oam.dev/v1alpha1
kind: ApplicationConfiguration
metadata:
  name: custom-single-app
  annotations:
    version: v1.0.0
    description: &amp;quot;Customized version of single-app&amp;quot;
spec:
  variables:
    - name: message
      value: &amp;quot;Well hello there&amp;quot;
    - name: domainName
      value: &amp;quot;www.example.com&amp;quot;
  components:
    - componentName: frontend
      instanceName: web-front-end
      parameterValues:
        - name: message
          value: &amp;quot;[fromVariable(message)]&amp;quot;
      traits:
        - name: Ingress
          properties:
            - name: host
              value: &amp;quot;[fromVaraible(domainName)]&amp;quot;
            - name: path
              value: &amp;quot;/&amp;quot;
      applicationScopes:
        - my-vpc-network

    - componentName: backend
      instanceName: database
      applicationScopes:
        - my-vpc-network
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这一组文件对象完成了几个任务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;创建了一个网络类型的 Application Scope，&lt;code&gt;my-vpc-network&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;引用一个叫做 &lt;code&gt;frontend&lt;/code&gt; 的组件，生成 &lt;code&gt;web-front-end&lt;/code&gt; 对象，并赋予参数 &lt;code&gt;message&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;为 &lt;code&gt;web-front-end&lt;/code&gt; 提供一个 Ingress 对象。&lt;/li&gt;
&lt;li&gt;将两个实例部署在 &lt;code&gt;my-vpc-network&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;后记&#34;&gt;后记&lt;/h2&gt;

&lt;p&gt;这几个对象里，基本形成了一个从交付物到运维的标准过程和定义，并且也直接使用 Rust 实现了基于这一规范的工具。符合这个规范的应用，就能能够在支持 OAM 的平台上进行运行和运维，虽然应用自身的结构、拓扑、构建、观测还有很多元素要实现，但是这些基础元素，应该已经能够发挥很好的示范效果了。&lt;/p&gt;

&lt;p&gt;印象里 OAM 的新闻稿里有一句话，OAM 和其他应用模型是不同的，它没有供应商锁定问题，因为它是构建在 Kubernetes 的基础之上的：Kubernetes 就是在锁定横行的环境下，利用更高层次的抽象来打破旧锁定，造就新锁定的。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 存活检测的危险性</title>
      <link>/post/k8s-liveness-probes-are-dangerous/</link>
      <pubDate>Fri, 18 Oct 2019 00:32:27 +0800</pubDate>
      <guid>/post/k8s-liveness-probes-are-dangerous/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://srcco.de/posts/kubernetes-liveness-probes-are-dangerous.html&#34; target=&#34;_blank&#34;&gt;LIVENESS PROBES ARE DANGEROUS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 的 &lt;code&gt;livenessProbe&lt;/code&gt; 是有一定危险性的。建议在用例清晰，并且理解足够深刻的情况下才使用这个功能。本文会涉及到存活检测以及就绪检测，并做出一些应该或者不该的建议。&lt;/p&gt;

&lt;p&gt;我的同事 Sandor 最近说了一下他看到的问题，其中包括了错误的存活检测和就绪检测的内容：&lt;/p&gt;

&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Most people that are new to &lt;a href=&#34;https://twitter.com/hashtag/kubernetes?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#kubernetes&lt;/a&gt; do the same mistakes:&lt;br&gt;- no readinessprobe &lt;br&gt;- wrong readinessprobe &lt;br&gt;- livenessprobe = readinessprobe &lt;br&gt;- non graceful shutdown &lt;br&gt;- graceful shutdown which is not graceful enough, best use lifecycle hook &lt;a href=&#34;https://t.co/OeNHqghhzC&#34;&gt;https://t.co/OeNHqghhzC&lt;/a&gt; &lt;br&gt;- pre fork mode&lt;/p&gt;&amp;mdash; Sandor Szücs (@sszuecs@hachyderm.io) (@sszuecs) &lt;a href=&#34;https://twitter.com/sszuecs/status/1175377157343907840?ref_src=twsrc%5Etfw&#34;&gt;September 21, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&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;a href=&#34;https://twitter.com/sszuecs/status/1175377157343907840&#34; target=&#34;_blank&#34;&gt;生命周期 Hook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fork 模式&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;错误的存活检测过程可能加重负载问题（雪崩式故障加上延长容器应用启动时间的风险），会引发其他负面问题，例如破坏依赖（参见我另一篇关于 &lt;a href=&#34;https://srcco.de/posts/k3s-outage-traefik-acme-lets-encrypt-local-path.html&#34; target=&#34;_blank&#34;&gt;K3s 和 ACME 速率限制的文章&lt;/a&gt;）。存活检测和外部数据健康检查的依赖是最差的情况：&lt;strong&gt;数据库的一点小问题会重启你的所有应用&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;在喊出“不要使用存活检测”口号之前，还是先看看存活检测和就绪检测的用途。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意下文很多来自 Zalando 的内部文档。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;就绪和存活检测&#34;&gt;就绪和存活检测&lt;/h2&gt;

&lt;p&gt;Kubernetes 提供了两个很棒的功能，分别是&lt;a href=&#34;https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/&#34; target=&#34;_blank&#34;&gt;就绪检测和存活检测&lt;/a&gt;。这两个功能会周期性的执行一个动作（比如说发出 HTTP 请求，打开一个 TCP 连接或者在容器中运行一个命令），从而确认你的应用正在如常运行。&lt;/p&gt;

&lt;p&gt;Kubernetes 使用&lt;strong&gt;就绪检测&lt;/strong&gt;来探测容器是否准备好开始接收流量。如果 Pod 中所有的容器都准备就绪，这个 Pod 就被当做是就绪状态。这种信号的一个用途就是来控制 Kubernetes 服务的后端 Pod（尤其是 Ingress）。&lt;/p&gt;

&lt;p&gt;Kubernetes 使用&lt;strong&gt;存活检测&lt;/strong&gt;来确定是否需要重启容器。例如存活检测能够检查到运行中应用的死锁，这种应用正在运行，但是不会有任何进展。重启这种容器能够在有 Bug 的情况下提高应用的可用性，然而也可能会引起级联故障（见后）。&lt;/p&gt;

&lt;p&gt;如果一个应用的存活或者就绪检测失败了，在尝试对其进行更新时，滚动更新的过程可能会挂死——K8s 会想要等待你的 Pod 进入就绪状态。&lt;/p&gt;

&lt;h2 id=&#34;举个栗子&#34;&gt;举个栗子&lt;/h2&gt;

&lt;p&gt;就绪检测会使用 HTTP 协议，检查 &lt;code&gt;/health&lt;/code&gt; 路径（缺省行为：10 秒钟间隔、1 秒钟超时、成功阈值 1，失败阈值：3）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
podTemplate:
  spec:
    containers:
    - name: my-container
      # ...
      readinessProbe:
        httpGet:
          path: /health
          port: 8080
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;建议&#34;&gt;建议&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;对于使用 HTTP 端点提供服务（尤其是 REST 服务）的微服务来说，严重建议&lt;strong&gt;定义一个就绪检测&lt;/strong&gt;，用来检测你的应用（Pod）是否准备好接收请求。&lt;/li&gt;
&lt;li&gt;确认你的就绪检测设置覆盖了 Web 服务器的真实端口。

&lt;ul&gt;
&lt;li&gt;如果你的就绪检测中使用了管理员端口（比如说 9090），如果主要 HTTP 端口（例如 8080）准备就绪，务必要确认该端点仅返回 OK。&lt;/li&gt;
&lt;li&gt;为就绪检测使用专属端口可能在主端口线程池的拥塞时报告错误的状态（主服务池已满，然而健康检测依然能够通过）。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;确保就绪检测包含了数据库的初始化和迁移过程。

&lt;ul&gt;
&lt;li&gt;最简单的方式就是仅在&lt;a href=&#34;https://flywaydb.org/&#34; target=&#34;_blank&#34;&gt;初始化&lt;/a&gt;完成之后才打开 HTTP 端口，也就是说，不设置健康状态，只是不启动 Web 服务器，直到数据库迁移完成。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;使用 http Get 访问知名的健康检查端点（例如 &lt;code&gt;/health&lt;/code&gt;）来完成就绪检测。&lt;/li&gt;
&lt;li&gt;理解缺省行为（缺省行为：10 秒钟间隔、1 秒钟超时、成功阈值 1，失败阈值 3）：

&lt;ul&gt;
&lt;li&gt;在大概 30 秒（3 次失败的检测）后，这个 Pod 会成为未就绪状态。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;如果你的技术栈（例如 Java/Spring）允许的话，可以使用不同的管理端口，把监控、检测端口和业务流量分开。

&lt;ul&gt;
&lt;li&gt;注意前面提到的线程问题。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;可以使用就绪检测来对应用进行缓存和预热，在容器完成预热之前，返回 503.

&lt;ul&gt;
&lt;li&gt;建议阅读 &lt;a href=&#34;https://sysdig.com/blog/whats-new-kubernetes-1-16/&#34; target=&#34;_blank&#34;&gt;1.6 中新增的 &lt;code&gt;stateupProbe&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;反对&#34;&gt;反对&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不要依赖外部因素，以免发生雪崩&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;例如有个有状态的 REST 服务，其中包含了 10 个依赖一个 Postgres 数据库的 Pod：如果数据库和网络不稳定，会导致 10 个 Pod 关闭，很显然这种反应会让结果变得更差。&lt;/li&gt;
&lt;li&gt;Spring Data 的缺省行为是检查数据库连接。&lt;/li&gt;
&lt;li&gt;这里所说的外部因素，还包含本集群中的其它 Pod，也就是说你的检测过程不应该依赖其它 Pod 的状态，以防止雪崩：

&lt;ul&gt;
&lt;li&gt;对于具有分布式状态的应用(例如跨 Pod 的内存缓存)，可能会有所不同。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在清楚为什么要使用存活检测，了解其后果之前，不用使用存活检测&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;存活检测能够帮助你恢复“卡死”的容器，但是如果你的应用够成熟，是不应该出现这种情况的——更好的对策是有目的地崩溃，以恢复正常状态。&lt;/li&gt;
&lt;li&gt;失败的存活检测会导致容器重启，可能会让性能问题更加恶化：容器重启是有停机时间的（损失时间至少是你的应用的启动时间，例如 30 秒），这样就会造成更多错误，让其它容器承受更多压力，可能引起更多容器的崩溃。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;如果使用存活检测，&lt;strong&gt;不要让存活检测和就绪检测使用同样的条件&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;可以让存活检测使用同样的健康检测方法，但是设置更高的 &lt;code&gt;failureThreshold&lt;/code&gt;（例如 3 次失败之后设置为未就绪，10 次失败后才让存活检测失败）&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要使用 &lt;code&gt;exec&lt;/code&gt; 检测&lt;/strong&gt;：这是一个已知问题，会导致僵尸进程。

&lt;ul&gt;
&lt;li&gt;参考 &lt;a href=&#34;https://www.youtube.com/watch?v=QKI-JRs2RIE&#34; target=&#34;_blank&#34;&gt;Datadog 的故障经历&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;在 Web App 中使用就绪检测来确定该 Pod 可以接受流量。&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;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;The 99% case for application developers is:&lt;br&gt;- use readinessprobe &lt;br&gt;- don’t use livenessprobe &lt;br&gt;I see too often both are the same, because of time no one checks what the difference is and if so it’s not easy enough to understand the impact.&lt;/p&gt;&amp;mdash; Sandor Szücs (@sszuecs@hachyderm.io) (@sszuecs) &lt;a href=&#34;https://twitter.com/sszuecs/status/1175655221382529025?ref_src=twsrc%5Etfw&#34;&gt;September 22, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


</description>
    </item>
    
    <item>
      <title>可能是最适合自定义的 Pipeline：Tekton</title>
      <link>/post/customizable-cicd-tekton/</link>
      <pubDate>Thu, 03 Oct 2019 20:37:11 +0800</pubDate>
      <guid>/post/customizable-cicd-tekton/</guid>
      <description>

&lt;p&gt;持续集成是云原生应用的支柱技术之一，因此在交付基于云原生的一些支撑产品的时候，CICD 是一个无法拒绝的需求。为了满足这种需要，自然而然会想到对 Jenkins(X) 或者 Gitlab 进行集成，然而这两个东西虽说功能强大，却也不是为了做螺丝钉而设计的，其中包含了大量的周边功能，并非我们产品的需要，并且其接口和 Pipeline 设计也不太容易复用和提供给用户进行定制，而 Tekton 这个东西就有趣多了：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Kubernetes 原生
Tekton 的所有配置都是使用 CRD 方式进行编写存储的，非常易于检索和使用。&lt;/li&gt;
&lt;li&gt;配置和流程分离
Tekton 的 Pipeline 和配置可以分开编写，使用名称进行引用。&lt;/li&gt;
&lt;li&gt;轻量级
核心的 Pipeline 非常轻便，适合作为组件进行集成，另外也有周边的 Dashboard、Trigger、CLI 等工具，能够进一步挖掘其潜力。&lt;/li&gt;
&lt;li&gt;可复用、组合的 Pipeline 构建方式
非常适合在集成过程中对 Pipeline 进行定制。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;安装过程非常轻松：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f \
    https://storage.googleapis.com/tekton-releases/latest/release.yaml
namespace/tekton-pipelines created
podsecuritypolicy.policy/tekton-pipelines created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-admin created
...
$ kubectl get pods -n tekton-pipelines
NAME                                           READY   STATUS    RESTARTS   AGE
tekton-pipelines-controller-5888756f5c-t5kgx   1/1     Running   0          2m10s
tekton-pipelines-webhook-7494f6f84b-gm92g      1/1     Running   0          2m10s
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;概念&#34;&gt;概念&lt;/h2&gt;

&lt;p&gt;今天的内容主要涉及几个 CRD：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Task：任务环节。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;TaskRun：Task 对象的运行参数。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Pipeline：Task 的组合。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;PipelineRun：Pipeline 的运行参数。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;hello-world&#34;&gt;Hello world&lt;/h2&gt;

&lt;p&gt;这里有个比 Hello world 稍稍复杂一点的小例子：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;下载一个文件。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;传递给下一个环节。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为什么不用官方例子呢？我想糊弄过 CI/CD/DevOps 的同学们应该都清楚，能使用容器、能执行 Shell、能获得输出、能传递文件，这几个能力加起来，足够冒充工具链小能手了。循序渐进并不适合心急的朋友们。&lt;/p&gt;

&lt;h3 id=&#34;下载文件并显示内容&#34;&gt;下载文件并显示内容&lt;/h3&gt;

&lt;p&gt;首先引入的是 Task 对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
  name: get-http-file
spec:
  steps:
    - name: show
      image: dustise/sleep
      command:
        - curl
      args:
        - &amp;quot;-s&amp;quot;
        - &amp;quot;https://httpbin.org/ip&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里定义了一个 Task CRD，使用 &lt;code&gt;kubectl apply -f&lt;/code&gt; 提交到集群，会看到 &lt;code&gt;task.tekton.dev/get-http-file created&lt;/code&gt; 的反馈信息。&lt;/p&gt;

&lt;p&gt;要运行这个环节，可以创建一个 TaskRun 对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: tekton.dev/v1alpha1
kind: TaskRun
metadata:
  name: get-http-file-run
spec:
  taskRef:
    name: get-http-file
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交之后，可以使用 &lt;code&gt;kubectl get taskrun get-http-file-run -o yaml&lt;/code&gt; 来查看任务执行状况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: tekton.dev/v1alpha1
kind: TaskRun
metadata:
...
status:
...
  conditions:
  - message: All Steps have completed executing
    reason: Succeeded
    status: &amp;quot;True&amp;quot;
    type: Succeeded
  podName: get-http-file-run-pod-51fddd
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里能看到很多任务执行信息，还能看到执行这个步骤的 Pod 名称，看看它的日志：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl logs -f get-http-file-run-pod-51fddd
{
  &amp;quot;origin&amp;quot;: &amp;quot;165.22.223.124, 165.22.223.124&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看来 CICD 过程中的日志输出和命令执行基本是有保障的，那么如何完成工件的传递呢？&lt;/p&gt;

&lt;h3 id=&#34;文件传递&#34;&gt;文件传递&lt;/h3&gt;

&lt;p&gt;通常我们都会想到使用 PVC 来进行文件存储和共享，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: trans
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;首先把上面的步骤命令行改为：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;command:
- curl
args:
- &amp;quot;-s&amp;quot;
- &amp;quot;-o&amp;quot;
- &amp;quot;/share/share.json&amp;quot;
- &amp;quot;https://httpbin.org/ip&amp;quot;
volumeMounts:
- name: trans
  mountPath: /share
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;第二个步骤就更加简单，只要显示文件内容即可：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
  name: display
spec:
  steps:
    - name: showcontent
      image: alpine
      command: [&amp;quot;cat&amp;quot;]
      args: [&amp;quot;/share/share.json&amp;quot;]
      volumeMounts:
        - name: trans
          mountPath: /share
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里需要使用 Pipeline 对象把步骤连接起来。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: tekton.dev/v1alpha1
kind: Pipeline
metadata:
  name: pipeline1
spec:
  tasks:
  - name: step1
    taskRef:
      name: download
  - name: step2
    runAfter: [step1]
    taskRef:
      name: display
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里的定义，使用 Pipeline 对象把两个步骤串联起来，其中使用 &lt;code&gt;taskRef&lt;/code&gt; 对我们定义的 &lt;code&gt;download&lt;/code&gt; 和 &lt;code&gt;display&lt;/code&gt; 两个 Task 对象进行引用，并且使用 &lt;code&gt;runAfter&lt;/code&gt; 数组定义先后顺序。&lt;/p&gt;

&lt;p&gt;和 &lt;code&gt;TaskRun&lt;/code&gt; 类似，Pipeline 定义之后，还需要用 PipelineRun 对象来执行一次，上面的 Task 中只定义了 &lt;code&gt;volumeMounts&lt;/code&gt;，具体的 Volume 就要在 PipelineRun 中定义：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: tekton.dev/v1alpha1
kind: PipelineRun
metadata:
  name: pprun1
spec:
  pipelineRef:
    name: pipeline1
  podTemplate:
    volumes:
      - name: trans
        persistentVolumeClaim:
          claimName: trans
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把 PipelineRun 提交到集群之后，就可以看到，Pipeline 开始运行，可以使用 &lt;code&gt;kubectl get&lt;/code&gt; 和 &lt;code&gt;kubectl logs&lt;/code&gt; 来查看运行情况。&lt;/p&gt;

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

&lt;p&gt;这个项目还是很符合它的名字的描述的，真的只有 Pipeline 而已，它的最重要职责就是用 CRD 进行解耦，用 Step-&amp;gt;Task-&amp;gt;Pipeline 的三级形式对 CICD 中的动作进行抽象和分离；用 Task/TaskRun 以及 Pipeline/PipelineRun/Resource 的组合，把运行环节和输入输出内容进行分离。这样一来，就提供了一个稳定、可重构和组合的过程引擎，以及可定制的执行能力。&lt;/p&gt;

&lt;p&gt;Tekton 还提供了一些其它周边项目，例如 Dashboard、Trigger 等，能给 Pipeline 项目提供一定的帮助。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>掀起 Traefik 2 的盖头来</title>
      <link>/post/touching-traefik-2/</link>
      <pubDate>Sun, 22 Sep 2019 17:54:03 +0800</pubDate>
      <guid>/post/touching-traefik-2/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://docs.traefik.io/v2.0/&#34; target=&#34;_blank&#34;&gt;Traefik 2.0&lt;/a&gt; 发布也有几天了，根据通稿来看，有几个新的功能值得注意：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;开始使用 &lt;a href=&#34;https://docs.traefik.io/v2.0/providers/kubernetes-crd/&#34; target=&#34;_blank&#34;&gt;CRD&lt;/a&gt; 来完成原本使用 &lt;a href=&#34;https://docs.traefik.io/v2.0/providers/kubernetes-ingress/&#34; target=&#34;_blank&#34;&gt;Ingress + 注解&lt;/a&gt;来完成的任务。&lt;/li&gt;
&lt;li&gt;加入了一个&lt;a href=&#34;https://docs.traefik.io/v2.0/middlewares/overview/&#34; target=&#34;_blank&#34;&gt;中间件&lt;/a&gt;概念，来提供丰富多样的控制能力。&lt;/li&gt;
&lt;li&gt;终于支持 TCP 了。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我的测试集群中经常会使用 Traefik 做 Ingress，又有 &lt;a href=&#34;https://github.com/containous/maesh&#34; target=&#34;_blank&#34;&gt;Maesh&lt;/a&gt; 加入 Service Mesh 的暖场战之中，所以 Traefik 2.0 还是值得看看的。它的文档做的还不太完善，着实花了一段时间来摸索，这里做个记录，方便后续的使用和学习。&lt;/p&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;我知道开篇说安装特别 Low，但是奇葩的是这个版本的安装文档居然只提到了一个 Docker 下的安装过程，只是在一个不起眼的角落里有一个&lt;a href=&#34;https://docs.traefik.io/v2.0/user-guides/crd-acme/&#34; target=&#34;_blank&#34;&gt;针对 Kubernetes 环境的安装清单&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;我做了一些修改，源码参见：&lt;/p&gt;

&lt;script type=&#34;application/javascript&#34; src=&#34;https://gist.github.com/fleeto/0666a58e83c5b201e61ab2fca779ace2.js&#34;&gt;&lt;/script&gt;


&lt;p&gt;这里有一点需要注意的，Traefik 缺省开放三个端口：80、443 和 8080，其中 8080 是管理端口。建议将其分为两个服务，把管理端口和业务端口做个隔离，方便使用不同策略进行访问控制。另外 Configmap 中加入了访问日志方便调试，可酌情关闭。&lt;/p&gt;

&lt;p&gt;例如下面把 Ingress 端口分配给 Loadbalancer，而管理端口仅设置为 ClusterIP。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
apiVersion: v1
kind: Service
metadata:
  name: traefik-ingress
  namespace: traefik-system
  labels:
    app: traefik
spec:
  ports:
  - name: https
    protocol: TCP
    port: 443
    targetPort: 443
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  type: LoadBalancer
  selector:
    app: traefik
---
apiVersion: v1
kind: Service
metadata:
  name: traefik-admin
  namespace: traefik-system
  labels:
    app: traefik
spec:
  ports:
  - name: admin
    protocol: TCP
    port: 8080
    targetPort: 8080
  type: ClusterIP
  selector:
    app: traefik
---
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;配置文件很简单，只要加入 Kubernetes 内部接口的端点即可：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;providers:
  kubernetescrd:
    endpoint: https://kubernetes.default
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后给 80 和 443 端口所在的 Loadbalancer 提供一个域名，用于后续的规则设置。&lt;/p&gt;

&lt;h2 id=&#34;部署测试服务&#34;&gt;部署测试服务&lt;/h2&gt;

&lt;p&gt;随便部署一个 HTTP 服务，例如 Istio 用到的 &lt;a href=&#34;https://raw.githubusercontent.com/istio/istio/master/samples/httpbin/httpbin.yaml&#34; target=&#34;_blank&#34;&gt;HTTPBIN&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个服务开放了 8000 端口提供 HTTP 服务，后面我们会用这个服务进行限流测试。&lt;/p&gt;

&lt;h3 id=&#34;开放-dashboard&#34;&gt;开放 Dashboard&lt;/h3&gt;

&lt;p&gt;前面把 Dashboard 的服务类型设置为 &lt;code&gt;ClusterIP&lt;/code&gt;，创建一个 CRD 就能够通过 Traefik 开放服务了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`traefik.microservice.xyz`)
    kind: Rule
    services:
    - name: traefik-admin
      port: 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里使用了一个表达式来对流量进行筛选，表达式目前的支持范围如下表所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/match.png&#34; alt=&#34;Match&#34; /&gt;&lt;/p&gt;

&lt;p&gt;接下来用了几个不同级别的对象：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entryPoints：开放端口，例如我们部署中的 &lt;code&gt;web&lt;/code&gt; 或 &lt;code&gt;websecure&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;routes：路由规则。&lt;/li&gt;
&lt;li&gt;match：一个匹配表达式，这里我们指定匹配域名 &lt;code&gt;traefik.microservice.xyz&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;name 和 Port：此处使用 Service 的定义&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;提交之后，就可以使用指定域名访问 Dashboard 了：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/dashboard.png&#34; alt=&#34;Dashboard&#34; /&gt;&lt;/p&gt;

&lt;p&gt;当然了，这个并不重要。&lt;/p&gt;

&lt;h3 id=&#34;开启一个插件&#34;&gt;开启一个插件&lt;/h3&gt;

&lt;p&gt;用同样的方法，给 HTTPBIN 开放一个路由：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: httpbin
spec:
  entryPoints:
  - web
  routes:
  - kind: Rule
    match: Host(`httpbin.microservice.xyz`)
    services:
    - name: httpbin
      port: 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在路由定义中，可以使用插件，对该路由的进出流量进行处理，例如重定向、认证、重试等功能。&lt;/p&gt;

&lt;p&gt;Ratelimit 的用法很简单，&lt;a href=&#34;https://docs.traefik.io/middlewares/ratelimit/&#34; target=&#34;_blank&#34;&gt;官网文档&lt;/a&gt;也有说明，我们模仿写一个：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: httpbin-traffic
spec:
  rateLimit:
    burst: 1
    average: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中的单位稍显死板，是 qps。但是怎么把这个对象加入到 Route 里面就不太明显了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: httpbin
...
    services:
    - name: httpbin
      port: 8000
    middlewares:
    - name: httpbin-traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交之后，可以看看 Dashboard 去看看这条规则的详情：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/route.png&#34; alt=&#34;route&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这条路由的 Middleware 中加入了新的元素，下面可以测试一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ siege httpbin.microservice.xyz/get
** SIEGE 4.0.4
** Preparing 25 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200     1.68 secs:     398 bytes ==&amp;gt; GET  /get
HTTP/1.1 429     1.68 secs:      17 bytes ==&amp;gt; GET  /get
HTTP/1.1 429     1.68 secs:      17 bytes ==&amp;gt; GET  /get
HTTP/1.1 429     1.68 secs:      17 bytes ==&amp;gt; GET  /get
HTTP/1.1 429     1.68 secs:      17 bytes ==&amp;gt; GET  /get
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到极少出现 200 的响应码，多数都是 429，证明限流生效了。&lt;/p&gt;

&lt;h2 id=&#34;废话&#34;&gt;废话&lt;/h2&gt;

&lt;p&gt;真是不知道 Kubernetes 用户哪里对不起 Traefik 了。。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kustomize Fomulas</title>
      <link>/post/kustomize-formulas/</link>
      <pubDate>Fri, 06 Sep 2019 01:50:43 +0800</pubDate>
      <guid>/post/kustomize-formulas/</guid>
      <description>

&lt;p&gt;项目地址 &lt;a href=&#34;https://github.com/fleeto/kustomize-patch-formulas&#34; target=&#34;_blank&#34;&gt;Kustomize-patch-formulas&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个项目有两个目的。&lt;/p&gt;

&lt;p&gt;第一点就是，既然有了 &lt;a href=&#34;https://github.com/kubernetes-sigs/kustomize&#34; target=&#34;_blank&#34;&gt;Kustomize&lt;/a&gt;，使用碎片来拼接复杂的 YAML 就很方便了，设想以下情况：&lt;/p&gt;

&lt;p&gt;一个只会写简单 Deployment 的小白，随时要去翻书，查查如何加入资源限制，如何加载存储卷，如何设置环境变量&amp;hellip;.，翻完用完，俩月之后同样的痛苦再来一次，周而复始。。好吧我就是那个小白。&lt;/p&gt;

&lt;p&gt;Kustomize 能不能让这个过程舒服一点？&lt;/p&gt;

&lt;p&gt;开始摸索 Kustomize 之后，我觉得有救了，只要我把需要的配置碎片积累下来，用的时候稍作修改，然后用 Kustomize 渲染出来，是不是就省了不少麻烦了？另外对一些巨型的 YAML，比如多个 Deployment 和 Service 和 VirtualService 什么的混成一个文件的情况，直接上手修改是很辛苦的，用这种方式也可以方便快捷的进行一些变更。&lt;/p&gt;

&lt;p&gt;更近一步的，这些补丁都很小，如果稍稍做点模板处理，用个小脚本加工成正式的 Patch，复用能力会进一步增强。&lt;/p&gt;

&lt;p&gt;第二点就是，就算不考虑这些问题，把常见的配置碎片搜集起来也是对 Kubernetes 运维工作很有帮助的。&lt;/p&gt;

&lt;h2 id=&#34;用法&#34;&gt;用法&lt;/h2&gt;

&lt;p&gt;例如我们有一个简单的 Deployment， &lt;code&gt;/tmp/deploy.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: flaskapp
        version: v1
    spec:
      containers:
        - name: flaskapp
          image: dustise/flaskapp
          imagePullPolicy: IfNotPresent
          env:
            - name: version
              value: v1
          ports:
            - containerPort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;克隆本项目之后，我们希望借助其中的补丁，为这个对象加入资源限制和 ServiceAccount：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ ./gen_kustomize.py -i /tmp/deploy.yaml -o kust
$ tree kust
kust
├── base
│   ├── deploy.yaml
│   └── kustomization.yaml
└── overlay
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个命令会为该文件创建一个 Kustomization。其中的 &lt;code&gt;-i&lt;/code&gt; 参数可以加入多个文件，都会被复制到目标目录（&lt;code&gt;kust&lt;/code&gt;）的 &lt;code&gt;base&lt;/code&gt; 之中。&lt;/p&gt;

&lt;p&gt;接下来向其中加入补丁：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ ./add_patch.py kubernetes/deployment/resource-patch.yaml \
    --set container_name=flaskapp deployment_name=flaskapp \
    limit_cpu=100m limit_memory=100Mi request_cpu=100m \
    request_memory=100Mi \
    -t kust
$ ./add_patch.py kubernetes/deployment/serviceaccount-patch.yaml \
    --set container_name=flaskapp \
    deployment_name=flaskapp service_account=default \
    -t kust
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里的第一个参数是补丁文件的位置，&lt;code&gt;--set&lt;/code&gt; 后面以 &lt;code&gt;key=value&lt;/code&gt; 的形式为模板进行赋值。模板的写法也很简单，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: $deployment_name
spec:
  replicas: 1
  template:
    spec:
      serviceAccount: $service_account
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用的是 Python 模板，只要简单的在变量前加入 &lt;code&gt;$&lt;/code&gt; 即可，因为对 Helm 深感疲惫，暂时不准备接入太复杂的模板方式，毕竟补丁本身越小越精确就越好。&lt;/p&gt;

&lt;p&gt;加入补丁之后，使用 &lt;code&gt;kustomize build kust/overlay&lt;/code&gt; 进行渲染：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: flaskapp
        version: v1
    spec:
      containers:
      - env:
        - name: version
          value: v1
        image: dustise/flaskapp
        imagePullPolicy: IfNotPresent
        name: flaskapp
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 100m
            memory: 100Mi
          requests:
            cpu: 100m
            memory: 100Mi
      serviceAccount: default
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，这里生成了完整的目标文件。&lt;/p&gt;

&lt;h2 id=&#34;需要贡献&#34;&gt;需要贡献&lt;/h2&gt;

&lt;p&gt;这个项目还非常低级，不堪一用，觉得这个小东西有意思的朋友可以提供：&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;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Funny Kind</title>
      <link>/post/funny-kind/</link>
      <pubDate>Thu, 29 Aug 2019 02:57:10 +0800</pubDate>
      <guid>/post/funny-kind/</guid>
      <description>&lt;p&gt;昨天的帖子可能是这个公众号诞生以来好笑的一幕——这个实操环节因为错上加错，收获了一个正确结果，以及好几个“在看”。&lt;/p&gt;

&lt;p&gt;事实上我们编写的 &lt;code&gt;config.yaml&lt;/code&gt;，在文末的命令行中并没有引用，所以&lt;strong&gt;其中的快速安装的结果完全是 Kind 的功劳，和之前的证书操作、镜像操作没有一毛钱的关系&lt;/strong&gt;。Kind 镜像自带了 Kubeadm 部署所需内容，因此它的的离线安装正常来说只需要：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker 正常运转&lt;/li&gt;
&lt;li&gt;本地有 Kind 镜像。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;私库之类的东西，完全不需要。&lt;/p&gt;

&lt;p&gt;昨天的错误原因主要在于：Docker 信任的 CA，并不被 Dind 所接受，所以要使用私库，首先要把自签发 CA 加入 Kind 镜像的信任列表，源码显示，它用的是 Ubuntu，因此可以这样构建一个新镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM kindest/node:v1.15.3
COPY ca.crt /usr/local/share/ca-certificates/ca.crt
RUN update-ca-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有了这个信任关系，就可以从容的使用私库了，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
kind create cluster --image kindest/node:customize \
    --config config/kind-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用这个镜像，就会加入对私库的信任关系，也就能够满足使用私库的需求了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>使用 Kind 构建离线集群安装包</title>
      <link>/post/kind-offline/</link>
      <pubDate>Tue, 27 Aug 2019 23:29:07 +0800</pubDate>
      <guid>/post/kind-offline/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://github.com/kubernetes-sigs/kind&#34; target=&#34;_blank&#34;&gt;Kind（Kubernetes in Docker）&lt;/a&gt; 是一个 Kubernetes 孵化项目，它使用 Docker 为节点进行 Kubernetes 的快速部署，可以方便的搭建 Kubernetes 集群，虽然简单，但也是通过 &lt;a href=&#34;https://landscape.cncf.io/selected=kind&#34; target=&#34;_blank&#34;&gt;Kubernetes 一致性认证&lt;/a&gt;的安装器，因此用来做测试还是比较可靠的。&lt;/p&gt;

&lt;p&gt;按照社区的一贯德行，安装过程虽然只要有个 Docker 就可以了，但是对网络有严重的依赖。在 Workshop 之类的场合，会受到很大限制。这种情况下，有一个离线包就会非常方便了。&lt;/p&gt;

&lt;h2 id=&#34;获取安装文件&#34;&gt;获取安装文件&lt;/h2&gt;

&lt;p&gt;本文会从一个新安装的 CentOS Mini 服务器为例，逐个步骤展示构建离线 Kind 安装包的过程。最后再安装一个&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这里假设这个服务器有“优秀”的互联网连接。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;docker-离线包&#34;&gt;Docker 离线包&lt;/h3&gt;

&lt;p&gt;首先我们要为 Docker 的离线安装准备好 RPM 包，新建一个目录 &lt;code&gt;rpms&lt;/code&gt;，用来下载安装包：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ yum install --downloadonly --downloaddir=./rpms \
    yum-utils device-mapper-persistent-data lvm2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;执行完毕之后，会在 rpms 目录中找到 Docker 的依赖包，接下来获取 Docker 的 RPM。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
$ yum --downloadonly --downloaddir=./rpms \
    install docker-ce docker-ce-cli containerd.io  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如此就获得了 Docker 自身以及所有的依赖 RPM 包。可以使用 rpm 直接安装。并启动 Docker 服务。&lt;/p&gt;

&lt;h3 id=&#34;kubeadm-和-kubectl&#34;&gt;Kubeadm 和 Kubectl&lt;/h3&gt;

&lt;p&gt;根据&lt;a href=&#34;https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-runtime&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cat &amp;lt;&amp;lt;EOF &amp;gt; /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF

$ yum install --downloadonly --downloaddir=./rpms \
kubectl kubeadm --disableexcludes=kubernetes
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里做了一点修改：不再安装 kubelet，也不启动服务，仅用于获取安装包。&lt;/p&gt;

&lt;h3 id=&#34;获取镜像&#34;&gt;获取镜像&lt;/h3&gt;

&lt;p&gt;使用 rpm 安装 kubeadm，开始获取安装所需的所有镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubeadm config images list --kubernetes-version=v1.15.3
k8s.gcr.io/kube-apiserver:v1.15.3
k8s.gcr.io/kube-controller-manager:v1.15.3
k8s.gcr.io/kube-scheduler:v1.15.3
k8s.gcr.io/kube-proxy:v1.15.3
k8s.gcr.io/pause:3.1
k8s.gcr.io/etcd:3.3.10
k8s.gcr.io/coredns:1.3.1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;拿到列表了就可以逐个拉取处理镜像，这里给个小脚本：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
for image in `kubeadm config images list --kubernetes-version=$1`
do
  image_name=`echo ${image} | sed &amp;quot;s/k8s.gcr.io.//g&amp;quot;`
  docker pull &amp;quot;${image}&amp;quot;
  docker tag &amp;quot;${image}&amp;quot; &amp;quot;${image_name}&amp;quot;
  file_name=&amp;quot;${image_name}.tgz&amp;quot;
  docker save &amp;quot;${image_name}&amp;quot; | gzip &amp;gt; &amp;quot;${file_name}&amp;quot;
  docker rmi &amp;quot;${image}&amp;quot;
  docker rmi &amp;quot;${image_name}&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样我们就有了所有 Kubeadm 所需的镜像。另外我们还需要两个辅助镜像：&lt;code&gt;kindest/node:v1.15.3&lt;/code&gt; 以及 &lt;code&gt;registry:2.7.1&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&#34;cfssl-和-kind&#34;&gt;CFSSL 和 Kind&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ wget https://github.com/kubernetes-sigs/kind/releases/download/v0.5.1/kind-linux-amd6
$ wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
$ wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样我们已经获得了所有安装所需的文件。可以进入离线步骤了。&lt;/p&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;h3 id=&#34;rpm-安装&#34;&gt;RPM 安装&lt;/h3&gt;

&lt;p&gt;把前面获得的 Kubectl 安装起来。&lt;/p&gt;

&lt;h3 id=&#34;签发证书&#34;&gt;签发证书&lt;/h3&gt;

&lt;p&gt;使用 CFSSL 生成证书，首先生成 CA。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ca-csr.json&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;CN&amp;quot;: &amp;quot;Kubernetes CA&amp;quot;,
    &amp;quot;key&amp;quot;: {
        &amp;quot;algo&amp;quot;: &amp;quot;rsa&amp;quot;,
        &amp;quot;size&amp;quot;: 2048
    },
    &amp;quot;ca&amp;quot;: {
       &amp;quot;expiry&amp;quot;: &amp;quot;87600h&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;cfssl gencert -initca ca-csr.json | cfssljson -bare ca&lt;/code&gt; 生成 CA 证书。&lt;/p&gt;

&lt;p&gt;把证书加入信任列表：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ update-ca-trust enable
$ cp ca.pem /etc/pki/ca-trust/source/anchors/
$ update-ca-trust extract
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;更新之后，需要重启 Docker 服务，让 Docker 也信任这一 CA。&lt;/p&gt;

&lt;p&gt;然后是生成镜像库所需的服务器证书：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;config.json&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;signing&amp;quot;: {
        &amp;quot;default&amp;quot;: {
            &amp;quot;expiry&amp;quot;: &amp;quot;87600h&amp;quot;
        },
        &amp;quot;profiles&amp;quot;: {
            &amp;quot;server&amp;quot;: {
                &amp;quot;expiry&amp;quot;: &amp;quot;87600h&amp;quot;,
                &amp;quot;usages&amp;quot;: [
                    &amp;quot;signing&amp;quot;,
                    &amp;quot;key encipherment&amp;quot;,
                    &amp;quot;server auth&amp;quot;,
                    &amp;quot;client auth&amp;quot;
                ]
            },
            &amp;quot;user&amp;quot;: {
                &amp;quot;expiry&amp;quot;: &amp;quot;87600h&amp;quot;,
                &amp;quot;usages&amp;quot;: [
                    &amp;quot;signing&amp;quot;,
                    &amp;quot;key encipherment&amp;quot;,
                    &amp;quot;client auth&amp;quot;
                ]
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;server.json&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;CN&amp;quot;: &amp;quot;Registry&amp;quot;,
    &amp;quot;hosts&amp;quot;: [
      &amp;quot;127.0.0.1&amp;quot;,&amp;quot;10.211.55.10&amp;quot;
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来签发证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cfssl gencert -ca ca.pem -ca-key ca-key.pem -config=config.json \
     -profile=server server.json | cfssljson -bare registry
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;这里的 IP 地址要跟本地地址对应。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;启动镜像库&#34;&gt;启动镜像库&lt;/h3&gt;

&lt;p&gt;使用 &lt;code&gt;docker load -i&lt;/code&gt;，载入了 registry 镜像，把仓库运行起来：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
docker run -d \
   --name=&amp;quot;registry2&amp;quot; \
   -v &amp;quot;$(pwd)/storage&amp;quot;:/var/lib/registry \
   -p 5000:5000 \
  -v &amp;quot;$(pwd)/certs&amp;quot;:/certs \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.pem \
  -e REGISTRY_HTTP_TLS_KEY=/certs/registry-key.pem \
   --restart=always \
   registry:2.7.1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如此就在 5000 端口启动了一个镜像库。&lt;/p&gt;

&lt;h3 id=&#34;推送镜像到私库&#34;&gt;推送镜像到私库&lt;/h3&gt;

&lt;p&gt;把前面的所有镜像都载入 Docker，Tag 之后推入私库，最后在私库里就有了一系列的镜像，例如 &lt;code&gt;10.211.55.10:5000/pause:3.1&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&#34;安装集群&#34;&gt;安装集群&lt;/h3&gt;

&lt;p&gt;为了能够使用私库的镜像，要对 Kind 进行一点配置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
kubeadmConfigPatches:
- |
  apiVersion: kubeadm.k8s.io/v1beta2
  kind: ClusterConfiguration
  metadata:
    name: config
  networking:
    serviceSubnet: 10.0.0.0/16
  imageRepository: 10.211.55.10:5000
  nodeRegistration:
    kubeletExtraArgs:
      pod-infra-container-image: 10.211.55.10:5000/pause:3.1
- |
  apiVersion: kubeadm.k8s.io/v1beta2
  kind: InitConfiguration
  metadata:
    name: config
  networking:
    serviceSubnet: 10.0.0.0/16
  imageRepository: 10.211.55.10:5000
nodes:
- role: control-plane
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kind create cluster --image kindest/node:v1.15.3
Creating cluster &amp;quot;kind&amp;quot; ...
 ✓ Ensuring node image (kindest/node:v1.15.3) 🖼
 ✓ Preparing nodes 📦
 ✓ Creating kubeadm config 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Cluster creation complete. You can now use the cluster with:

export KUBECONFIG=&amp;quot;$(kind get kubeconfig-path --name=&amp;quot;kind&amp;quot;)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如此这般，Kind 集群就启动了。我们可以测试一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ export KUBECONFIG=&amp;quot;$(kind get kubeconfig-path --name=&amp;quot;kind&amp;quot;)&amp;quot;
$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                         READY   STATUS    RESTARTS   AGE
kube-system   coredns-5c98db65d4-lkg92                     1/1     Running   0          72m
kube-system   coredns-5c98db65d4-rhv7g                     1/1     Running   0          72m
kube-system   etcd-kind-control-plane                      1/1     Running   0          71m
kube-system   kindnet-892gh                                1/1     Running   1          72m
kube-system   kube-apiserver-kind-control-plane            1/1     Running   0          71m
kube-system   kube-controller-manager-kind-control-plane   1/1     Running   0          71m
kube-system   kube-proxy-nd4ml                             1/1     Running   0          72m
kube-system   kube-scheduler-kind-control-plane            1/1     Running   0          71m
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;离线环境下部署镜像&#34;&gt;离线环境下部署镜像&lt;/h2&gt;

&lt;p&gt;Kind 提供了一个 load 命令，可以把镜像载入所有“节点”，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kind load docker-image 10.211.55.10:5000/registry:2.7.1
$ kubectl run registry --image=10.211.55.10:5000/registry:2.7.1 --replicas=2
$ kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/registry created
$ kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
registry-6b7648c4f4-g8zxr   1/1     Running   0          24s
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;后记&#34;&gt;后记&lt;/h2&gt;

&lt;p&gt;很明显，这些步骤是可以自动化的，只需要分发一个安装包，就能够很方便的快速部署、删除一整套的 Kubernetes，可以用随需部署的方式完成环境的搭建和删除。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>kubectl exec 的来龙去脉</title>
      <link>/post/how-kubectl-exec-works/</link>
      <pubDate>Tue, 27 Aug 2019 18:16:58 +0800</pubDate>
      <guid>/post/how-kubectl-exec-works/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://erkanerol.github.io/post/how-kubectl-exec-works/&#34; target=&#34;_blank&#34;&gt;How does &amp;lsquo;kubectl exec&amp;rsquo; work?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://twitter.com/erkan_erol_&#34; target=&#34;_blank&#34;&gt;Erkan Erol&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;上周五，一个同事问了我一个问题——如何使用 client-go 在 Pod 中执行命令。我答不出来，而且注意到我从来没想过 &lt;code&gt;kubectl exec&lt;/code&gt; 的实现机制。我对这个问题有一点认识，但又不是很确定。我记下了这个题目，进行了一番探索，在阅读了大量博客、文档和代码之后，收获了很多知识。本文中我会分享这个过程中的理解和发现。&lt;/p&gt;

&lt;h2 id=&#34;环境&#34;&gt;环境&lt;/h2&gt;

&lt;p&gt;我使用 &lt;a href=&#34;https://github.com/ecomm-integration-ballerina/kubernetes-cluster&#34; target=&#34;_blank&#34;&gt;https://github.com/ecomm-integration-ballerina/kubernetes-cluster&lt;/a&gt; 中的工具在我的 Macbook 上创建 Kubernetes 集群。缺省配置不允许运行 &lt;code&gt;kubectl exec&lt;/code&gt;，我在 Kubelet 配置中修改了 IP 地址，具体原因参见博客：&lt;a href=&#34;https://medium.com/@joatmon08/playing-with-kubeadm-in-vagrant-machines-part-2-bac431095706&#34; target=&#34;_blank&#34;&gt;Playing with kubeadm in Vagrant Machines&lt;/a&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;Any machine = my MacBook
IP of master node = 192.168.205.10
IP of worker node = 192.168.205.11
API server port = 6443
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;组件&#34;&gt;组件&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/components.png&#34; alt=&#34;components&#34; /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubectl exec&lt;/code&gt; 进程：在我们运行 &lt;code&gt;kubectl exec&lt;/code&gt; 时，会启动一个进程。可以在任何一台能够访问到 Kubernetes API Server 的机器上运行该命令。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;api-server&lt;/code&gt;：运行在 Master 上，提供开放的 Kubernetes API，它是 Kubernetes 控制平面的前端。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubelet&lt;/code&gt;：在集群所有节点上都会运行这个进程，它负责让容器以 Pod 的模式运行。&lt;/li&gt;
&lt;li&gt;容器运行时：负责运行容器，例如 Docker、cri-o、containerd&amp;hellip;&lt;/li&gt;
&lt;li&gt;内核：工作节点上的操作系统内核，负责管理进程。&lt;/li&gt;
&lt;li&gt;目标容器：组成 Pod 的容器，在工作节点上运行。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;探索&#34;&gt;探索&lt;/h2&gt;

&lt;h3 id=&#34;客户端的活动&#34;&gt;客户端的活动&lt;/h3&gt;

&lt;p&gt;在缺省命名空间中创建一个 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;# kubectl run exec-test-nginx --image=nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;执行 &lt;code&gt;sleep 5000&lt;/code&gt;，来进行观察：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;# ps -ef |grep kubectl
501  8507  8409   0  7:19PM ttys000    0:00.13 kubectl exec -it exec-test-nginx-6558988d5-fgxgg -- sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查该进程的网络活动，会看到连接到 API Server 的通信（192.168.205.10.6443）&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;$ netstat -atnv |grep 8507
tcp4       0      0  192.168.205.1.51673    192.168.205.10.6443    ESTABLISHED 131072 131768   8507      0 0x0102 0x00000020
tcp4       0      0  192.168.205.1.51672    192.168.205.10.6443    ESTABLISHED 131072 131768   8507      0 0x0102 0x00000028
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再看看代码。&lt;code&gt;kubectl&lt;/code&gt; 发起了一个包含 &lt;code&gt;exec&lt;/code&gt; 子资源的 POST 请求：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;req := restClient.Post().
        Resource(&amp;quot;pods&amp;quot;).
        Name(pod.Name).
        Namespace(pod.Namespace).
        SubResource(&amp;quot;exec&amp;quot;)
req.VersionedParams(&amp;amp;corev1.PodExecOptions{
        Container: containerName,
        Command:   p.Command,
        Stdin:     p.Stdin,
        Stdout:    p.Out != nil,
        Stderr:    p.ErrOut != nil,
        TTY:       t.Raw,
}, scheme.ParameterCodec)

return p.Executor.Execute(&amp;quot;POST&amp;quot;, req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/rest-request.png&#34; alt=&#34;rest-request.png&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;master-上的活动&#34;&gt;Master 上的活动&lt;/h3&gt;

&lt;p&gt;在 API Server 端当然也能观察到请求的情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;handler.go:143] kube-apiserver: POST &amp;quot;/api/v1/namespaces/default/pods/exec-test-nginx-6558988d5-fgxgg/exec&amp;quot; satisfied by gorestful with webservice /api/v1
upgradeaware.go:261] Connecting to backend proxy (intercepting redirects) https://192.168.205.11:10250/exec/default/exec-test-nginx-6558988d5-fgxgg/exec-test-nginx?command=sh&amp;amp;input=1&amp;amp;output=1&amp;amp;tty=1
Headers: map[Connection:[Upgrade] Content-Length:[0] Upgrade:[SPDY/3.1] User-Agent:[kubectl/v1.12.10 (darwin/amd64) kubernetes/e3c1340] X-Forwarded-For:[192.168.205.1] X-Stream-Protocol-Version:[v4.channel.k8s.io v3.channel.k8s.io v2.channel.k8s.io channel.k8s.io]]
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;HTTP 请求中包含了协议升级的请求，SPDY 允许在单个 TCP 连接上复用独立的 stdin/stdout/stderr/spdy-error 流。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;API Server 收到请求，绑定到 &lt;code&gt;PodExecOptions&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// PodExecOptions is the query options to a Pod&#39;s remote exec call
type PodExecOptions struct {
        metav1.TypeMeta

        // Stdin if true indicates that stdin is to be redirected for the exec call
        Stdin bool

        // Stdout if true indicates that stdout is to be redirected for the exec call
        Stdout bool

        // Stderr if true indicates that stderr is to be redirected for the exec call
        Stderr bool

        // TTY if true indicates that a tty will be allocated for the exec call
        TTY bool

        // Container in which to execute the command.
        Container string

        // Command is the remote command to execute; argv array; not executed within a shell.
        Command []string
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;为了执行必要的动作，API Server 需要知道联系地址：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// ExecLocation returns the exec URL for a pod container. If opts.Container is blank
// and only one container is present in the pod, that container is used.
func ExecLocation(
        getter ResourceGetter,
        connInfo client.ConnectionInfoGetter,
        ctx context.Context,
        name string,
        opts *api.PodExecOptions,
) (*url.URL, http.RoundTripper, error) {
        return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, &amp;quot;exec&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当然这个端点是来自 Node：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;nodeName := types.NodeName(pod.Spec.NodeName)
if len(nodeName) == 0 {
        // If pod has not been assigned a host, return an empty location
        return nil, nil, errors.NewBadRequest(fmt.Sprintf(&amp;quot;pod %s does not have a host assigned&amp;quot;, name))
}
nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubelet 提供了一个端口，API Server 可以进行连接：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// GetConnectionInfo retrieves connection info from the status of a Node API object.
func (k *NodeConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeName types.NodeName) (*ConnectionInfo, error) {
        node, err := k.nodes.Get(ctx, string(nodeName), metav1.GetOptions{})
        if err != nil {
                return nil, err
        }

        // Find a kubelet-reported address, using preferred address type
        host, err := nodeutil.GetPreferredNodeAddress(node, k.preferredAddressTypes)
        if err != nil {
                return nil, err
        }

        // Use the kubelet-reported port, if present
        port := int(node.Status.DaemonEndpoints.KubeletEndpoint.Port)
        if port &amp;lt;= 0 {
                port = k.defaultPort
        }

        return &amp;amp;ConnectionInfo{
                Scheme:    k.scheme,
                Hostname:  host,
                Port:      strconv.Itoa(port),
                Transport: k.transport,
        }, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/architecture/master-node-communication/#apiserver-to-kubelet&#34; target=&#34;_blank&#34;&gt;API Server to Kubelet&lt;/a&gt;
Kubelet 开放的是一个 HTTPS 端点。缺省情况下 API Server 是不会验证 Kubelet 的服务证书的，这样这个连接就存在遭到中间人攻击的隐患，在不受信任的或者公开的网络上运行是不安全的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;现在，API Server 得到了端点地址，打开连接：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// Connect returns a handler for the pod exec proxy
func (r *ExecREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
        execOpts, ok := opts.(*api.PodExecOptions)
        if !ok {
                return nil, fmt.Errorf(&amp;quot;invalid options object: %#v&amp;quot;, opts)
        }
        location, transport, err := pod.ExecLocation(r.Store, r.KubeletConn, ctx, name, execOpts)
        if err != nil {
                return nil, err
        }
        return newThrottledUpgradeAwareProxyHandler(location, transport, false, true, true, responder), nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看看 Master 上发生了什么。&lt;/p&gt;

&lt;p&gt;首先确定一下工作节点的 IP，这里是 &lt;code&gt;192.168.205.11&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get nodes k8s-node-1 -o wide
NAME         STATUS   ROLES    AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
k8s-node-1   Ready    &amp;lt;none&amp;gt;   9h    v1.15.3   192.168.205.11   &amp;lt;none&amp;gt;        Ubuntu 16.04.6 LTS   4.4.0-159-generic   docker://17.3.3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后查找 Kubelet 的端口号：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get nodes k8s-node-1 -o jsonpath=&#39;{.status.daemonEndpoints.kubeletEndpoint}&#39;
map[Port:10250]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来看看是不是存在到工作节点的连接？看到连接之后，如果杀掉 &lt;code&gt;exec&lt;/code&gt; 进程，这个连接就会消失。这说明这个连接是 API Server 响应 &lt;code&gt;exec&lt;/code&gt; 请求而生成的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ netstat -atn |grep 192.168.205.11
tcp        0      0 192.168.205.10:37870    192.168.205.11:10250    ESTABLISHED
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/api-server-to-kubelet.png&#34; alt=&#34;api-server-to-kubelet.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;目前为止，kubectl 和 API Server 之间的连接还存在，并且 API Server 和 Kubelet 之间也建立了连接。&lt;/p&gt;

&lt;h3 id=&#34;工作节点上的活动&#34;&gt;工作节点上的活动&lt;/h3&gt;

&lt;p&gt;接下来我们连接到工作节点上，看看这里发生了什么。&lt;/p&gt;

&lt;p&gt;首先我们同样能看到连接，第二行显示了 Master 的地址：&lt;code&gt;192.168.205.10&lt;/code&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;// worker node
$ netstat -atn |grep 10250
tcp6       0      0 :::10250                :::*                    LISTEN
tcp6       0      0 192.168.205.11:10250    192.168.205.10:37870    ESTABLISHED
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们的 sleep 命令呢？也可以看到：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;// worker node
$ ps -afx
...
31463 ?        Sl     0:00      \_ docker-containerd-shim 7d974065bbb3107074ce31c51f5ef40aea8dcd535ae11a7b8f2dd180b8ed583a /var/run/docker/libcontainerd/7d974065bbb3107074ce31c51
31478 pts/0    Ss     0:00          \_ sh
31485 pts/0    S+     0:00              \_ sleep 5000
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubelet 是如何做到的？&lt;/p&gt;

&lt;p&gt;Kubelet 提供了一个服务端口，用来响应 API Server 的请求：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// Server is the library interface to serve the stream requests.
type Server interface {
        http.Handler

        // Get the serving URL for the requests.
        // Requests must not be nil. Responses may be nil iff an error is returned.
        GetExec(*runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error)
        GetAttach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error)
        GetPortForward(*runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error)

        // Start the server.
        // addr is the address to serve on (address:port) stayUp indicates whether the server should
        // listen until Stop() is called, or automatically stop after all expected connections are
        // closed. Calling Get{Exec,Attach,PortForward} increments the expected connection count.
        // Function does not return until the server is stopped.
        Start(stayUp bool) error
        // Stop the server, and terminate any open connections.
        Stop() error
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubelet 为 exec 请求生成一个响应端点：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func (s *server) GetExec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
        if err := validateExecRequest(req); err != nil {
                return nil, err
        }
        token, err := s.cache.Insert(req)
        if err != nil {
                return nil, err
        }
        return &amp;amp;runtimeapi.ExecResponse{
                Url: s.buildURL(&amp;quot;exec&amp;quot;, token),
        }, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;它返回的不是命令结果，而是一个用于通信的端点：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;type ExecResponse struct {
        // Fully qualified URL of the exec streaming server.
        Url                  string   `protobuf:&amp;quot;bytes,1,opt,name=url,proto3&amp;quot; json:&amp;quot;url,omitempty&amp;quot;`
        XXX_NoUnkeyedLiteral struct{} `json:&amp;quot;-&amp;quot;`
        XXX_sizecache        int32    `json:&amp;quot;-&amp;quot;`
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubelet 实现了一个 CRI 规范中的 &lt;code&gt;RuntimeServiceClient&lt;/code&gt; 接口：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RuntimeServiceClient interface {
        // Version returns the runtime name, runtime version, and runtime API version.
        Version(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*VersionResponse, error)
        // RunPodSandbox creates and starts a pod-level sandbox. Runtimes must ensure
        // the sandbox is in the ready state on success.
        RunPodSandbox(ctx context.Context, in *RunPodSandboxRequest, opts ...grpc.CallOption) (*RunPodSandboxResponse, error)
        // StopPodSandbox stops any running process that is part of the sandbox and
        // reclaims network resources (e.g., IP addresses) allocated to the sandbox.
        // If there are any running containers in the sandbox, they must be forcibly
        // terminated.
        // This call is idempotent, and must not return an error if all relevant
        // resources have already been reclaimed. kubelet will call StopPodSandbox
        // at least once before calling RemovePodSandbox. It will also attempt to
        // reclaim resources eagerly, as soon as a sandbox is not needed. Hence,
        // multiple StopPodSandbox calls are expected.
        StopPodSandbox(ctx context.Context, in *StopPodSandboxRequest, opts ...grpc.CallOption) (*StopPodSandboxResponse, error)
        // RemovePodSandbox removes the sandbox. If there are any running containers
        // in the sandbox, they must be forcibly terminated and removed.
        // This call is idempotent, and must not return an error if the sandbox has
        // already been removed.
        RemovePodSandbox(ctx context.Context, in *RemovePodSandboxRequest, opts ...grpc.CallOption) (*RemovePodSandboxResponse, error)
        // PodSandboxStatus returns the status of the PodSandbox. If the PodSandbox is not
        // present, returns an error.
        PodSandboxStatus(ctx context.Context, in *PodSandboxStatusRequest, opts ...grpc.CallOption) (*PodSandboxStatusResponse, error)
        // ListPodSandbox returns a list of PodSandboxes.
        ListPodSandbox(ctx context.Context, in *ListPodSandboxRequest, opts ...grpc.CallOption) (*ListPodSandboxResponse, error)
        // CreateContainer creates a new container in specified PodSandbox
        CreateContainer(ctx context.Context, in *CreateContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error)
        // StartContainer starts the container.
        StartContainer(ctx context.Context, in *StartContainerRequest, opts ...grpc.CallOption) (*StartContainerResponse, error)
        // StopContainer stops a running container with a grace period (i.e., timeout).
        // This call is idempotent, and must not return an error if the container has
        // already been stopped.
        // TODO: what must the runtime do after the grace period is reached?
        StopContainer(ctx context.Context, in *StopContainerRequest, opts ...grpc.CallOption) (*StopContainerResponse, error)
        // RemoveContainer removes the container. If the container is running, the
        // container must be forcibly removed.
        // This call is idempotent, and must not return an error if the container has
        // already been removed.
        RemoveContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*RemoveContainerResponse, error)
        // ListContainers lists all containers by filters.
        ListContainers(ctx context.Context, in *ListContainersRequest, opts ...grpc.CallOption) (*ListContainersResponse, error)
        // ContainerStatus returns status of the container. If the container is not
        // present, returns an error.
        ContainerStatus(ctx context.Context, in *ContainerStatusRequest, opts ...grpc.CallOption) (*ContainerStatusResponse, error)
        // UpdateContainerResources updates ContainerConfig of the container.
        UpdateContainerResources(ctx context.Context, in *UpdateContainerResourcesRequest, opts ...grpc.CallOption) (*UpdateContainerResourcesResponse, error)
        // ReopenContainerLog asks runtime to reopen the stdout/stderr log file
        // for the container. This is often called after the log file has been
        // rotated. If the container is not running, container runtime can choose
        // to either create a new log file and return nil, or return an error.
        // Once it returns error, new container log file MUST NOT be created.
        ReopenContainerLog(ctx context.Context, in *ReopenContainerLogRequest, opts ...grpc.CallOption) (*ReopenContainerLogResponse, error)
        // ExecSync runs a command in a container synchronously.
        ExecSync(ctx context.Context, in *ExecSyncRequest, opts ...grpc.CallOption) (*ExecSyncResponse, error)
        // Exec prepares a streaming endpoint to execute a command in the container.
        Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error)
        // Attach prepares a streaming endpoint to attach to a running container.
        Attach(ctx context.Context, in *AttachRequest, opts ...grpc.CallOption) (*AttachResponse, error)
        // PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
        PortForward(ctx context.Context, in *PortForwardRequest, opts ...grpc.CallOption) (*PortForwardResponse, error)
        // ContainerStats returns stats of the container. If the container does not
        // exist, the call returns an error.
        ContainerStats(ctx context.Context, in *ContainerStatsRequest, opts ...grpc.CallOption) (*ContainerStatsResponse, error)
        // ListContainerStats returns stats of all running containers.
        ListContainerStats(ctx context.Context, in *ListContainerStatsRequest, opts ...grpc.CallOption) (*ListContainerStatsResponse, error)
        // UpdateRuntimeConfig updates the runtime configuration based on the given request.
        UpdateRuntimeConfig(ctx context.Context, in *UpdateRuntimeConfigRequest, opts ...grpc.CallOption) (*UpdateRuntimeConfigResponse, error)
        // Status returns the status of the runtime.
        Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 gRPC 通过 CRI 调用方法：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;type runtimeServiceClient struct {
        cc *grpc.ClientConn
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func (c *runtimeServiceClient) Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error) {
        out := new(ExecResponse)
        err := c.cc.Invoke(ctx, &amp;quot;/runtime.v1alpha2.RuntimeService/Exec&amp;quot;, in, out, opts...)
        if err != nil {
                return nil, err
        }
        return out, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;容器运行时负责实现 &lt;code&gt;RuntimeServiceServer&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// RuntimeServiceServer is the server API for RuntimeService service.
type RuntimeServiceServer interface {
        // Version returns the runtime name, runtime version, and runtime API version.
        Version(context.Context, *VersionRequest) (*VersionResponse, error)
        // RunPodSandbox creates and starts a pod-level sandbox. Runtimes must ensure
        // the sandbox is in the ready state on success.
        RunPodSandbox(context.Context, *RunPodSandboxRequest) (*RunPodSandboxResponse, error)
        // StopPodSandbox stops any running process that is part of the sandbox and
        // reclaims network resources (e.g., IP addresses) allocated to the sandbox.
        // If there are any running containers in the sandbox, they must be forcibly
        // terminated.
        // This call is idempotent, and must not return an error if all relevant
        // resources have already been reclaimed. kubelet will call StopPodSandbox
        // at least once before calling RemovePodSandbox. It will also attempt to
        // reclaim resources eagerly, as soon as a sandbox is not needed. Hence,
        // multiple StopPodSandbox calls are expected.
        StopPodSandbox(context.Context, *StopPodSandboxRequest) (*StopPodSandboxResponse, error)
        // RemovePodSandbox removes the sandbox. If there are any running containers
        // in the sandbox, they must be forcibly terminated and removed.
        // This call is idempotent, and must not return an error if the sandbox has
        // already been removed.
        RemovePodSandbox(context.Context, *RemovePodSandboxRequest) (*RemovePodSandboxResponse, error)
        // PodSandboxStatus returns the status of the PodSandbox. If the PodSandbox is not
        // present, returns an error.
        PodSandboxStatus(context.Context, *PodSandboxStatusRequest) (*PodSandboxStatusResponse, error)
        // ListPodSandbox returns a list of PodSandboxes.
        ListPodSandbox(context.Context, *ListPodSandboxRequest) (*ListPodSandboxResponse, error)
        // CreateContainer creates a new container in specified PodSandbox
        CreateContainer(context.Context, *CreateContainerRequest) (*CreateContainerResponse, error)
        // StartContainer starts the container.
        StartContainer(context.Context, *StartContainerRequest) (*StartContainerResponse, error)
        // StopContainer stops a running container with a grace period (i.e., timeout).
        // This call is idempotent, and must not return an error if the container has
        // already been stopped.
        // TODO: what must the runtime do after the grace period is reached?
        StopContainer(context.Context, *StopContainerRequest) (*StopContainerResponse, error)
        // RemoveContainer removes the container. If the container is running, the
        // container must be forcibly removed.
        // This call is idempotent, and must not return an error if the container has
        // already been removed.
        RemoveContainer(context.Context, *RemoveContainerRequest) (*RemoveContainerResponse, error)
        // ListContainers lists all containers by filters.
        ListContainers(context.Context, *ListContainersRequest) (*ListContainersResponse, error)
        // ContainerStatus returns status of the container. If the container is not
        // present, returns an error.
        ContainerStatus(context.Context, *ContainerStatusRequest) (*ContainerStatusResponse, error)
        // UpdateContainerResources updates ContainerConfig of the container.
        UpdateContainerResources(context.Context, *UpdateContainerResourcesRequest) (*UpdateContainerResourcesResponse, error)
        // ReopenContainerLog asks runtime to reopen the stdout/stderr log file
        // for the container. This is often called after the log file has been
        // rotated. If the container is not running, container runtime can choose
        // to either create a new log file and return nil, or return an error.
        // Once it returns error, new container log file MUST NOT be created.
        ReopenContainerLog(context.Context, *ReopenContainerLogRequest) (*ReopenContainerLogResponse, error)
        // ExecSync runs a command in a container synchronously.
        ExecSync(context.Context, *ExecSyncRequest) (*ExecSyncResponse, error)
        // Exec prepares a streaming endpoint to execute a command in the container.
        Exec(context.Context, *ExecRequest) (*ExecResponse, error)
        // Attach prepares a streaming endpoint to attach to a running container.
        Attach(context.Context, *AttachRequest) (*AttachResponse, error)
        // PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
        PortForward(context.Context, *PortForwardRequest) (*PortForwardResponse, error)
        // ContainerStats returns stats of the container. If the container does not
        // exist, the call returns an error.
        ContainerStats(context.Context, *ContainerStatsRequest) (*ContainerStatsResponse, error)
        // ListContainerStats returns stats of all running containers.
        ListContainerStats(context.Context, *ListContainerStatsRequest) (*ListContainerStatsResponse, error)
        // UpdateRuntimeConfig updates the runtime configuration based on the given request.
        UpdateRuntimeConfig(context.Context, *UpdateRuntimeConfigRequest) (*UpdateRuntimeConfigResponse, error)
        // Status returns the status of the runtime.
        Status(context.Context, *StatusRequest) (*StatusResponse, error)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/kubelet-to-container-runtime.png&#34; alt=&#34;kubelet-to-container-runtime.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;既然如此，我们就该看看 Kubelet 和容器运行时之间的连接。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;// worker node
$ ss -a -p |grep kubelet
...
u_str  ESTAB      0      0       * 157937                * 157387                users:((&amp;quot;kubelet&amp;quot;,pid=5714,fd=33))
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在 Kubelet（PID=5714）和 Docker 之间有一个新的 Unix Socket 连接：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;// worker node
$ ss -a -p |grep 157387
...
u_str  ESTAB      0      0       * 157937                * 157387                users:((&amp;quot;kubelet&amp;quot;,pid=5714,fd=33))
u_str  ESTAB      0      0      /var/run/docker.sock 157387                * 157937                users:((&amp;quot;dockerd&amp;quot;,pid=1186,fd=14))
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;是 Docker 守护进程（PID 1186）执行了我们的命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;// worker node.
$ ps -afx
...
 1186 ?        Ssl    0:55 /usr/bin/dockerd -H fd://
17784 ?        Sl     0:00      \_ docker-containerd-shim 53a0a08547b2f95986402d7f3b3e78702516244df049ba6c5aa012e81264aa3c /var/run/docker/libcontainerd/53a0a08547b2f95986402d7f3
17801 pts/2    Ss     0:00          \_ sh
17827 pts/2    S+     0:00              \_ sleep 5000
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;容器运行时的活动&#34;&gt;容器运行时的活动&lt;/h3&gt;

&lt;p&gt;看看 cri-o 的源码，了解一下相关内容。运行逻辑和 Docker 类似。&lt;/p&gt;

&lt;p&gt;它提供了一个服务，实现了 &lt;code&gt;RuntimeServiceServer&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// Server implements the RuntimeService and ImageService
type Server struct {
        config          libconfig.Config
        seccompProfile  *seccomp.Seccomp
        stream          StreamService
        netPlugin       ocicni.CNIPlugin
        hostportManager hostport.HostPortManager

        appArmorProfile string
        hostIP          string
        bindAddress     string

        *lib.ContainerServer
        monitorsChan      chan struct{}
        defaultIDMappings *idtools.IDMappings
        systemContext     *types.SystemContext // Never nil

        updateLock sync.RWMutex

        seccompEnabled  bool
        appArmorEnabled bool
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// Exec prepares a streaming endpoint to execute a command in the container.
func (s *Server) Exec(ctx context.Context, req *pb.ExecRequest) (resp *pb.ExecResponse, err error) {
        const operation = &amp;quot;exec&amp;quot;
        defer func() {
                recordOperation(operation, time.Now())
                recordError(operation, err)
        }()

        resp, err = s.getExec(req)
        if err != nil {
                return nil, fmt.Errorf(&amp;quot;unable to prepare exec endpoint: %v&amp;quot;, err)
        }

        return resp, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;链条的最后一环，容器运行时在工作节点上执行命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// ExecContainer prepares a streaming endpoint to execute a command in the container.
func (r *runtimeOCI) ExecContainer(c *Container, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize &amp;lt;-chan remotecommand.TerminalSize) error {
        processFile, err := prepareProcessExec(c, cmd, tty)
        if err != nil {
                return err
        }
        defer os.RemoveAll(processFile.Name())

        args := []string{rootFlag, r.root, &amp;quot;exec&amp;quot;}
        args = append(args, &amp;quot;--process&amp;quot;, processFile.Name(), c.ID())
        execCmd := exec.Command(r.path, args...)
        if v, found := os.LookupEnv(&amp;quot;XDG_RUNTIME_DIR&amp;quot;); found {
                execCmd.Env = append(execCmd.Env, fmt.Sprintf(&amp;quot;XDG_RUNTIME_DIR=%s&amp;quot;, v))
        }
        var cmdErr, copyError error
        if tty {
                cmdErr = ttyCmd(execCmd, stdin, stdout, resize)
        } else {
                if stdin != nil {
                        // Use an os.Pipe here as it returns true *os.File objects.
                        // This way, if you run &#39;kubectl exec &amp;lt;pod&amp;gt; -i bash&#39; (no tty) and type &#39;exit&#39;,
                        // the call below to execCmd.Run() can unblock because its Stdin is the read half
                        // of the pipe.
                        r, w, err := os.Pipe()
                        if err != nil {
                                return err
                        }
                        go func() { _, copyError = pools.Copy(w, stdin) }()

                        execCmd.Stdin = r
                }
                if stdout != nil {
                        execCmd.Stdout = stdout
                }
                if stderr != nil {
                        execCmd.Stderr = stderr
                }

                cmdErr = execCmd.Run()
        }

        if copyError != nil {
                return copyError
        }
        if exitErr, ok := cmdErr.(*exec.ExitError); ok {
                return &amp;amp;utilexec.ExitErrorWrapper{ExitError: exitErr}
        }
        return cmdErr
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/container-runtime-to-kernel.png&#34; alt=&#34;container-runtime-to-kernel&#34; /&gt;&lt;/p&gt;

&lt;p&gt;最后，内核执行了任务：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/kernel-puts.png&#34; alt=&#34;kernel-puts.png&#34; /&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;API Server 会向 Kubelet 发起连接。&lt;/li&gt;
&lt;li&gt;在 exec 结束之前，连接持续存在。

&lt;ul&gt;
&lt;li&gt;Kubectl 和 API Server 之间&lt;/li&gt;
&lt;li&gt;API Server 和 Kubelet 之间&lt;/li&gt;
&lt;li&gt;Kubelet 和容器运行时之间&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Kubectl 或者 API Server 无法在工作节点上运行任何东西。Kubelet 可以通过和容器运行时的互动来完成任务。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kustomize 中的增删改查</title>
      <link>/post/crud-with-kustomize/</link>
      <pubDate>Sat, 17 Aug 2019 23:12:56 +0800</pubDate>
      <guid>/post/crud-with-kustomize/</guid>
      <description>

&lt;p&gt;在之前的文章中，我曾经说过，相对于 Helm Chart，Kustomize 具有更平缓的学习曲线，更灵活的派生方式，当然这也不是没有代价的，Kustomize 的分享方式和规范性都弱于 Helm Chart，而且上游对下游也是相当缺乏控制能力的。&lt;/p&gt;

&lt;p&gt;本文会从实际出发，列出一系列的场景，看看拿到一个上游应用的标准 YAML 之后，如何使用 Kustomize 对其进行增删改查，形成新的 Kubernetes 资源组，完成对派生应用的定义。&lt;/p&gt;

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

&lt;p&gt;首先我们创建实验目录 &lt;code&gt;lab/base&lt;/code&gt;，在其中生成一组 YAML 文件，作为后续实验的基础（&lt;code&gt;base&lt;/code&gt;），这是一个很简单的应用，只有一个 Deployment：&lt;/p&gt;

&lt;p&gt;deploy.yaml：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: flaskapp
        version: v1
    spec:
      containers:
        - name: flaskapp
          image: dustise/flaskapp
          imagePullPolicy: IfNotPresent
          env:
            - name: version
              value: v1
          ports:
            - containerPort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果使用 Kubectl 提交，会在当前命名空间中新建一个 Deployment 和一个 Pod，当然我们暂时不会这么做。&lt;/p&gt;

&lt;p&gt;接下来是创建一个 Kustomize 的定义，作为后续实验的基础，基础定义很简单，它甚至可能不会由上游应用的开发者编写，而是下游的定制者用它来做一个基础。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kustomization.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;resources:
- deploy.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对它进行渲染，会毫无意外的出现一个和 &lt;code&gt;deploy.yaml&lt;/code&gt; 一样的输出内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kubectl kustomize base
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: flaskapp
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;增&#34;&gt;增&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;创建一个新的工作目录，&lt;code&gt;lab/overlays/c&lt;/code&gt;，本节的内容都会使用这个目录来完成。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在继承上游应用的 YAML 时，经常需要向其中加入定制元素，比如加入新的 Ingress、Service、Configmap 等，还有可能新增一些对象内的定义。下面的两个例子分别展示了 Kustomize 完成这类任务的方法。&lt;/p&gt;

&lt;h3 id=&#34;加入新对象&#34;&gt;加入新对象&lt;/h3&gt;

&lt;p&gt;上面的 Base 中，有了一个孤零零的 Deployment，多数的业务应用还是需要有个 Service 提供服务的，我们就给它加入一个新对象。&lt;/p&gt;

&lt;p&gt;在 &lt;code&gt;c&lt;/code&gt; 目录中，新建一个 &lt;code&gt;service.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Service
metadata:
  name: flaskapp
  labels:
    app: flaskapp
spec:
  selector:
    app: flaskapp
    version: v1
  ports:
    - name: http
      port: 80
      targetPort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后新建一个 &lt;code&gt;kustomization.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../../base
resources:
- service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;执行命令 &lt;code&gt;kustomize build overlays/c&lt;/code&gt;，可以看到，在原有 &lt;code&gt;Deployment&lt;/code&gt; 的基础上，新增了一个服务。&lt;/p&gt;

&lt;h3 id=&#34;新增字段&#34;&gt;新增字段&lt;/h3&gt;

&lt;p&gt;很多应用都会声明运行时的资源需求，但是我们的 &lt;code&gt;base&lt;/code&gt; 中并没有这个字段，Kustomize 可以用补丁的方式加入这个字段。&lt;/p&gt;

&lt;p&gt;首先创建一个补丁文件 &lt;code&gt;overlays/c/resource-patch.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: flaskapp
        resources:
          requests:
            memory: &amp;quot;64Mi&amp;quot;
            cpu: &amp;quot;250m&amp;quot;
          limits:
            memory: &amp;quot;64Mi&amp;quot;
            cpu: &amp;quot;250m&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的补丁文件用最简单的字段赋值标识出了要打补丁的位置，并加入了资源限制。&lt;/p&gt;

&lt;p&gt;修改 &lt;code&gt;overlays/c/kustomization.yaml&lt;/code&gt;，加入补丁：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../../base
resources:
- service.yaml
patchesStrategicMerge:
- resource-patch.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次使用 &lt;code&gt;kustomize build overlays/c&lt;/code&gt;，或者直接使用 &lt;code&gt;kubectl apply -k overlays/c&lt;/code&gt;，都能看到，资源限制已经应用。&lt;/p&gt;

&lt;h3 id=&#34;创建-configmap-并加载&#34;&gt;创建 Configmap 并加载&lt;/h3&gt;

&lt;p&gt;在 &lt;code&gt;overlays/c&lt;/code&gt; 下面新建一个配置文件 &lt;code&gt;config.ini&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;key=value
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;新增的 &lt;code&gt;configMapGenerator&lt;/code&gt; 要求使用 &lt;code&gt;config.ini&lt;/code&gt; 生成一个 ConfigMap。&lt;/p&gt;

&lt;p&gt;然后再做一个补丁文件 &lt;code&gt;env-patch.yaml&lt;/code&gt;，用来加载 Config：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: flaskapp
        env:
        - name: CONFIG
          valueFrom:
            configMapKeyRef:
              name: config-ini
              key: config.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;修改 &lt;code&gt;kustomization.yaml&lt;/code&gt;，加入我们的 Configmap 和补丁，结果为如下内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../../base
resources:
- service.yaml
configMapGenerator:
- name: config-ini
  files:
    - config.ini
patchesStrategicMerge:
- resource-patch.yaml
- env-patch.yaml
generatorOptions:
  disableNameSuffixHash: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;kubectl apply -k overlays/c&lt;/code&gt; 之后，会看到 Pod 中加入了对 Configmap 的引用，进入 Pod 也能看到新增的环境变量，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl exec -it flaskapp-5b886b8dfc-qq68w env | grep CONFIG
CONFIG=key=value
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;删&#34;&gt;删&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;创建一个新的工作目录，&lt;code&gt;lab/overlays/d&lt;/code&gt;，本节的内容都会使用这个目录来完成。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;使用 &lt;code&gt;patchesStrategicMerge&lt;/code&gt; 的特定语法，可以删除指定内容。&lt;/p&gt;

&lt;h3 id=&#34;删除一个对象&#34;&gt;删除一个对象&lt;/h3&gt;

&lt;p&gt;在 &lt;code&gt;c&lt;/code&gt; 案例中新建的 Service 对象，我并不想要，如何通过 &lt;code&gt;Kustomize&lt;/code&gt; 删掉？同样也使用打补丁的方式来解决。首先创建一个继承自 &lt;code&gt;overlays/c&lt;/code&gt; 的 Kustomization，在 &lt;code&gt;overlays/d&lt;/code&gt; 中创建 &lt;code&gt;kustomization.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../c
patchesStrategicMerge:
- remove-svc.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;以及 &lt;code&gt;remove-svc.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Service
apiVersion: v1
metadata:
  name: flaskapp
$patch: delete
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;运行 &lt;code&gt;kustomize build overlays/d&lt;/code&gt;，可以看到，输出内容中的 Service 不见了。&lt;/p&gt;

&lt;h3 id=&#34;删除一个字段&#34;&gt;删除一个字段&lt;/h3&gt;

&lt;p&gt;接下来我们用类似的方法，删除前面设置的环境变量：&lt;/p&gt;

&lt;p&gt;新建一个 &lt;code&gt;remove-field.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: flaskapp
        version: v1
    spec:
      containers:
        - name: flaskapp
          image: dustise/flaskapp
          env:
          - name: CONFIG
            $patch: delete
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再把补丁加入 &lt;code&gt;kustomization.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../c
patchesStrategicMerge:
- remove-svc.yamlƒ
- remove-field.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次 &lt;code&gt;kustomize build overlays/d&lt;/code&gt;，可以看到新的环境变量已经被删除。&lt;/p&gt;

&lt;h2 id=&#34;改&#34;&gt;改&lt;/h2&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;创建一个新的工作目录，&lt;code&gt;lab/overlays/u&lt;/code&gt;，本节的内容都会使用这个目录来完成。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;p&gt;除了 &lt;code&gt;patchesStrategicMerge&lt;/code&gt; 之外，&lt;code&gt;kustomize&lt;/code&gt; 还提供了一些方便快捷的方式能够对特定内容进行修改。&lt;/p&gt;

&lt;h3 id=&#34;修改名字&#34;&gt;修改名字&lt;/h3&gt;

&lt;p&gt;可以用 namePrefix 和 nameSuffix 修改对象的名字，例如我们要给&lt;strong&gt;所有&lt;/strong&gt;名字加上 &lt;code&gt;overlay-u&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kustomization.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../c
namePrefix: overlay-u
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来可以看看结果：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -k overlays/u --dry-run -o yaml | grep -E &amp;quot;^\s+name:&amp;quot;
    name: overlay-u-config-ini
    name: overlay-u-flaskapp
    name: overlay-u-flaskapp
                name: overlay-u-config-ini
          name: flaskapp
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;输出内容中，前三个分别是 ConfigMap、Service 以及 Deployment，第四行中对 Configmap 的引用也修改成了新的名字。&lt;/p&gt;

&lt;p&gt;除了修改名字之外，还有以下几个暴力的修改功能：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;命名空间：namespaces&lt;/li&gt;
&lt;li&gt;注解：commonAnnotations&lt;/li&gt;
&lt;li&gt;标签：commonLabels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;说这几个东西比较野蛮的原因是：他们的应用范围是整个 Kustomization，例如下面的定制：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../c
namePrefix: overlay-u
commonLabels:
    auto: label
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;生成之后会发现，从 Service、Deployment、Configmap 一直到 Pod，都会打上新标签。&lt;/p&gt;

&lt;h3 id=&#34;修改镜像&#34;&gt;修改镜像&lt;/h3&gt;

&lt;p&gt;修改镜像也是个常见任务，相对上面的几个暴力修改，镜像的修改相对精确一点——从一种镜像修改为另一种，例如我们把 flaskapp 的镜像更换为 &lt;code&gt;nginx:alpine&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kustomization.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../c
namePrefix: overlay-u
commonLabels:
    auto: label
images:
- name: dustise/flaskapp
  newName: nginx
  newTag: alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;修改副本数量&#34;&gt;修改副本数量&lt;/h3&gt;

&lt;p&gt;修改副本数量，指定资源名称以及副本数量即可：&lt;/p&gt;

&lt;p&gt;例如如下的 &lt;code&gt;kustomization.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../c
replicas:
- name: flaskapp
  count: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 &lt;code&gt;kustomize build overlays/u&lt;/code&gt;，会看到 Deployment 的该字段已经改为 2。&lt;/p&gt;

&lt;h2 id=&#34;查&#34;&gt;查&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;创建一个新的工作目录，&lt;code&gt;lab/overlays/r&lt;/code&gt;，本节的内容都会使用这个目录来完成。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;前面提到，&lt;code&gt;namePrefix&lt;/code&gt; 修改了 ConfigMap 的名称之后，加载这个 CM 的 Deployment 也会修改引用的名称，然而如果在其它位置，例如命令行参数或者环境变量中引用的服务名称之类，就无法完成修改了。假设我们给 Deployment 中加入一个新的环境变量，叫 &lt;code&gt;deploy&lt;/code&gt;，用如下的 &lt;code&gt;kustomization.yaml&lt;/code&gt; 实现：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;bases:
- ../../base
namePrefix: overlay-r-
patchesStrategicMerge:
- env-patch.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中引用的 &lt;code&gt;env-patch.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: flaskapp
        env:
        - name: deploy
          value: flaskapp
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;kustomize build overlays/r&lt;/code&gt; 渲染出来之后，很明显的 value 已经无法对应：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: overlay-r-flaskapp
...
      - env:
        - name: svc
          value: flaskapp
        - name: version
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在 &lt;code&gt;kustomization.yaml&lt;/code&gt; 中加入 &lt;code&gt;vars&lt;/code&gt;，用一系列条件选择一个对象来进行引用，为变量赋值：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
vars:
- name: DEPLOY_NAME
  objref:
    kind: Deployment
    name: flaskapp
    apiVersion: extensions/v1beta1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把 &lt;code&gt;env-patch.yaml&lt;/code&gt; 中的环境变量定义修改为引用新变量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
        - name: deploy
          value: $(DEPLOY_NAME)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次 &lt;code&gt;kustomize build overlays/r&lt;/code&gt;，会看到符合预期的输出内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: overlay-r-flaskapp
...
        - name: deploy
          value: overlay-r-flaskapp

&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;命令行&#34;&gt;命令行&lt;/h2&gt;

&lt;p&gt;除了上面说的这些声明式的用法之外，&lt;code&gt;kustomize&lt;/code&gt; 还提供了更为即兴的 &lt;code&gt;edit&lt;/code&gt; 命令，可以进行现场修改，进一步的提高了灵活（混乱）程度，有兴趣的读者可以自行摸索。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Helm 和 Kustomize：不只是含谷量的区别</title>
      <link>/post/helm-vs-kustomize/</link>
      <pubDate>Thu, 08 Aug 2019 00:57:37 +0800</pubDate>
      <guid>/post/helm-vs-kustomize/</guid>
      <description>

&lt;p&gt;Kustomize 问世的时候，我是比较鄙视的——非要造个谷歌的轮子么？不过最近抽出时间熟悉了一下 Kustomize，发现我还是带了有色眼镜。二者功能虽然有所重叠，但是工作思路的差别还是很大的，下面就简单做一点比较，权当引玉之砖。&lt;/p&gt;

&lt;h2 id=&#34;helm&#34;&gt;Helm&lt;/h2&gt;

&lt;p&gt;Helm 是 Kubernetes 中的第一个对应用程序进行管理的支撑工具，经常会拿来同 Yum、apt 等工具进行类比。Helm 由几个不同的组件构成：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI：客户端工具，有几大功能

&lt;ul&gt;
&lt;li&gt;从 Chart 服务器获取列表、搜索 Chart 项目&lt;/li&gt;
&lt;li&gt;安装 Chart&lt;/li&gt;
&lt;li&gt;构建 Chart&lt;/li&gt;
&lt;li&gt;充当 Chart 服务器&lt;/li&gt;
&lt;li&gt;和 Tiller 协同管理应用生命周期&lt;/li&gt;
&lt;li&gt;渲染 Chart 为 Kubernetes 生成 YAML&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Tiller：需要用特定授权和 API Server 进行通信，Kubernetes 集群内外部署都可以。

&lt;ul&gt;
&lt;li&gt;它是 Helm 的内鬼，负责接收来自 Cli 的指令，完成对集群内应用生命周期的控制。&lt;/li&gt;
&lt;li&gt;3.0 中将取消 Tiller。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Repository：

&lt;ul&gt;
&lt;li&gt;是存储 Helm Chart 的仓库，可以被 Cli 工具缓存、检索，CLI 也可以获取 Chart 进行后续操作。&lt;/li&gt;
&lt;li&gt;和 Tiller 一样，Repository 也不是必要组件，CLI 完全可以仅在本地工作。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;helm-chart&#34;&gt;Helm Chart&lt;/h3&gt;

&lt;p&gt;Helm 使用 Chart 对应用程序进行描述，它使用 Go Template 对应用部署所需的 YAML 进行抽象，形成应用部署模板，在需要进行部署时，可以编写 yaml 为模板中的变量进行赋值，也可以在 Helm CLI 的命令行中使用 &lt;code&gt;--set name=value&lt;/code&gt; 的方式来对简单变量进行赋值，完成赋值之后，可以选择使用 &lt;code&gt;helm template&lt;/code&gt; 指令将 Chart + Value 的组合渲染成为 YAML 供 &lt;code&gt;kubectl&lt;/code&gt; 使用，也可以使用 &lt;code&gt;helm install&lt;/code&gt; 直接通过 Tiller 进行安装。&lt;/p&gt;

&lt;h3 id=&#34;helm-的特色&#34;&gt;Helm 的特色&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;强大的生命周期管理：有 Tiller 的帮助，可以实现对应用程序实例（Release）的查询、安装、卸载、升级、回滚等复杂操作。&lt;/li&gt;
&lt;li&gt;严格的基础版本管控：Chart 是一种模板，Chart 的用户仅能通过对 &lt;code&gt;values&lt;/code&gt; 的控制来定制应用的部署行为，模板中没有提供变量的位置，是无法在下游直接进行变更的。&lt;/li&gt;
&lt;li&gt;方便的命令行：对于简单变量，可以在部署的同时直接指定内容，方便部署。&lt;/li&gt;
&lt;li&gt;插件和工具：Helm 拥趸众多，提供了不少用于 CICD 或者其它方面辅助功能的插件和工具。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;kustomize&#34;&gt;Kustomize&lt;/h2&gt;

&lt;p&gt;Kustomize 是一个新晋选手，只有一个 CLI 工具，在 Kubernetes 1.14 之后，甚至这唯一的工具也成为 kubectl 的一部分，可以说是很轻量级了。&lt;/p&gt;

&lt;h3 id=&#34;base-overlay&#34;&gt;Base + Overlay&lt;/h3&gt;

&lt;p&gt;在 Kustomize 的文档中明确说明：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;kustomize is a command line tool supporting template-free, structured customization of declarative configuration targetted to k8s-style objects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;它放弃了对模板的要求，改用 Base + Overlay 的方式对应用的原始 YAML 进行&lt;code&gt;派生&lt;/code&gt;。&lt;code&gt;Overlay&lt;/code&gt;，顾名思义，就是覆盖。Kustomize 的 Overlay 可以在 Base 的基础上，通过对 &lt;code&gt;resource&lt;/code&gt;、&lt;code&gt;generator&lt;/code&gt;、&lt;code&gt;transformer&lt;/code&gt; 等的定义，形成新的应用定义，不论 Base
还是 Overlay，都可以通过 &lt;code&gt;kustomize build&lt;/code&gt; 生成有效的 YAML。&lt;/p&gt;

&lt;h3 id=&#34;kustomize-的特色&#34;&gt;Kustomize 的特色&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;功能简单清晰，kubectl 直接支持。&lt;/li&gt;
&lt;li&gt;不考虑派生，仅作为应用的 YAML 组织方式也很有帮助。&lt;/li&gt;
&lt;li&gt;也有自己的插件系统。例如可以用简单的 YAML 定义，使用文件生成 Configmap/Secret。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;比较&#34;&gt;比较&lt;/h2&gt;

&lt;p&gt;Kustomize 自称因为去掉了模板语法，更易使用，对此我保留看法，如果仅就入门使用来看，二者差异并不大。&lt;/p&gt;

&lt;p&gt;Tiller 和 Repository 都并非必须，因此在部署上，Kustomize 的优势也不是很大。&lt;/p&gt;

&lt;p&gt;我认为他们的区别主要在工作流程上：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Helm 的基础流程比较&lt;code&gt;瀑布&lt;/code&gt;：定义 Chart-&amp;gt;填充-&amp;gt;运行，在 Chart 中没有定义的内容是无法更改的；&lt;/li&gt;
&lt;li&gt;Kustomize 的用法比较&lt;code&gt;迭代&lt;/code&gt;：Base 和 Overlay 都是可以独立运作的，增加新对象，或者对编写 Base 时未预料的内容进行变更，都不在话下。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如我们定义了一个很基础的应用，由 Deployment + Service 组成，如果后续部署中需要完成两个变更：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;新建 Ingress 对象&lt;/li&gt;
&lt;li&gt;修改镜像地址/名称/TAG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在 Helm 中需要：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 Chart 中加入对 Ingress 的定义&lt;/li&gt;
&lt;li&gt;用变量控制 Ingress 是否进行渲染&lt;/li&gt;
&lt;li&gt;Ingress 模板应该包含特定的主机名、注解等变量&lt;/li&gt;
&lt;li&gt;把镜像也定义成变量&lt;/li&gt;
&lt;li&gt;在 Values.yaml 中对这些变量进行赋值。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;而在 Kustomize 中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;无需对 Base 进行修改&lt;/li&gt;
&lt;li&gt;直接在新的 Overlay 中写入 Ingress Resource&lt;/li&gt;
&lt;li&gt;使用内置的 &lt;code&gt;image transformer&lt;/code&gt; 替换原有镜像&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;要公开发布一个较为复杂的应用，例如 &lt;code&gt;Istio&lt;/code&gt;，编写良好的 Chart 能给用户很大帮助，用户在缺失一点发挥空间的情况下，通过对 &lt;code&gt;values.yaml&lt;/code&gt; 的阅读，就能对这种复杂的部署产生一个较为深入的认识。&lt;/p&gt;

&lt;p&gt;如果是常见的业务应用，因为不同部署之间的差异不大，但是未必可以提前做好变化限制，用 Kustomize 可能会是一个更好的选择。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Helm：&lt;code&gt;https://helm.sh/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Kustomize：&lt;code&gt;https://github.com/kubernetes-sigs/kustomize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Helm 3.0：&lt;code&gt;https://helm.sh/blog/helm-3-preview-pt1/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Helm 工具：&lt;code&gt;https://www.infoq.cn/article/UemPsPu_AlzemkGEqb4Z&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Awesome Helm：&lt;code&gt;https://github.com/cdwv/awesome-helm&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes Deployment 终极指南</title>
      <link>/post/kubernetes-deployments-the-ultimate-guide/</link>
      <pubDate>Sun, 28 Jul 2019 23:27:01 +0800</pubDate>
      <guid>/post/kubernetes-deployments-the-ultimate-guide/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://semaphoreci.com/blog/kubernetes-deployment&#34; target=&#34;_blank&#34;&gt;Kubernetes Deployments: The Ultimate Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://semaphoreci.com/author/jpetazzo&#34; target=&#34;_blank&#34;&gt;Jérôme Petazzoni&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;要把容器化的应用部署起来？在 Kubernetes 中部署容器化应用，总要涉及到 Deployment，这里有这个对象的所有内容。&lt;/p&gt;

&lt;p&gt;我们最早学会的 Kubernetes 命令之一就是 &lt;code&gt;kubectl run&lt;/code&gt;。具备 Docker 经验的用户，不免会用 &lt;code&gt;docker run&lt;/code&gt; 命令和这个命令进行对比，结论可能是：运行容器就是这么简单。&lt;/p&gt;

&lt;p&gt;我们来看看，在运行一个基本的 &lt;code&gt;kubectl run&lt;/code&gt; 命令的时候，都发生了些什么：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl run web --image=nginx
deployment.apps/web created
&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 all
NAME                       READY     STATUS    RESTARTS   AGE
pod/web-65899c769f-dhtdx   1/1       Running   0          11s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    &amp;lt;none&amp;gt;        443/TCP   46s

NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   1         1         1            1           11s

NAME                             DESIRED   CURRENT   READY     AGE
replicaset.apps/web-65899c769f   1         1         1         11s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们并没有看到容器，而是一组未知对象：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployment：&lt;code&gt;web&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;ReplicaSet：&lt;code&gt;web-65899c769f&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pod：&lt;code&gt;web-65899c769f-dhtdx&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;此处的 &lt;code&gt;kubernetes&lt;/code&gt; 服务可以忽略，它在我们运行命令之前就已经存在了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我只想要个容器！为什么看到了三个不同的对象？&lt;/p&gt;

&lt;p&gt;简单说来，这些 Kubernetes 对象能在不停服务的情况下，为应用提供渐进式部署、回滚以及伸缩的支持。&lt;/p&gt;

&lt;p&gt;初次见面难免会好奇：究竟是怎么回事？在了解这些问题之后，就会理解每个对象的角色和存在价值了。&lt;/p&gt;

&lt;p&gt;持续集成提升了对代码的信心。要把这种信心扩展到发布流程之中，部署操作就需要更多保障。&lt;/p&gt;

&lt;h2 id=&#34;容器和-pod&#34;&gt;容器和 Pod&lt;/h2&gt;

&lt;p&gt;在 Kubernetes 中，一个 Deployment 的最小单元不是容器，而是 Pod。Pod 是一组容器（当然这一组也可以只有一个），它们运行在同一台服务器中，并共享一些资源。&lt;/p&gt;

&lt;p&gt;例如 Pod 中的容器能够通过 &lt;code&gt;localhost&lt;/code&gt; 互相通信。在网络视角中，这些容器中的所有进程都是本地的。&lt;/p&gt;

&lt;p&gt;但是我们永远无法创建独立的容器：最相近的操作也只能是创建一个仅包含单一容器的一个 Pod。&lt;/p&gt;

&lt;p&gt;我们想让 Kubernetes 创建 NGINX，完整的台词是：“我要一个 Pod，其中只包含一个容器，这个容器运行的是 nginx 镜像”。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# pod-nginx.yml
# Create it with:
#    kubectl apply -f pod-nginx.yml
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
    - image: nginx
      name: nginx
      ports:
        - containerPort: 80
          name: http
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这就只有一个 Pod，那 ReplicaSet 和 Deployment 是怎么回事？&lt;/p&gt;

&lt;h2 id=&#34;指令和声明&#34;&gt;指令和声明&lt;/h2&gt;

&lt;p&gt;Kubernetes 是一个声明式系统（和指令式系统相对），这就意味着我们无法给它发出命令。我们不能说：“运行这个容器”。我们能做的只能是——描述我们需要的东西，然后等 Kubernetes 根据现有内容，同步为预期内容。&lt;/p&gt;

&lt;p&gt;打个比方，我们可以说：“我要一个 40 英尺高的有黄色门的蓝色容器”，Kubernetes 会为我们查找这种容器，如果找不到，就会创建一个；如果已经有了，但它是绿色红门的，Kubernetes 就会帮我们上色；如果已经有了完全符合要求的容器，因为现有内容和预期内容一致，所以 Kubernetes 什么都不会做。&lt;/p&gt;

&lt;p&gt;回到软件容器的话题，我们可以说：“我想要一个名字叫 &lt;code&gt;web&lt;/code&gt; 的 Pod，其中应该有单独的容器，运行的是 &lt;code&gt;nginx&lt;/code&gt; 镜像”。&lt;/p&gt;

&lt;p&gt;如果这个 Pod 不存在，Kubernetes 会创建出来。如果符合我们要求的 Pod 已经存在，Kubernetes 无需进行任何动作。&lt;/p&gt;

&lt;p&gt;基于这种思路，怎样对 &lt;code&gt;web&lt;/code&gt; 应用进行伸缩，来满足多容器或 Pod 的运行需要呢？&lt;/p&gt;

&lt;h2 id=&#34;replicaset-简化了-pod-的伸缩过程&#34;&gt;ReplicaSet 简化了 Pod 的伸缩过程&lt;/h2&gt;

&lt;p&gt;如果我们只有一个 Pod，我们想要更多的同样的 Pod，我们可能会给 Kubernetes 提出这样的要求：“我们需要一个叫做 &lt;code&gt;web2&lt;/code&gt; 的 Pod，具体要求是：&amp;hellip;”，然后重复之前的 Pod 规范。想要多少 Pod，就重复执行多少次。&lt;/p&gt;

&lt;p&gt;这明显很不方便，我们要自己跟踪所有的 Pod，确保它们都同步了正确的状态，并符合特定的规范。&lt;/p&gt;

&lt;p&gt;Kubernetes 提供了高级一些的抽象来简化这个过程：&lt;code&gt;ReplicaSet&lt;/code&gt;。&lt;code&gt;ReplicaSet&lt;/code&gt; 的对象结构和 Pod 很相似，只不过它还有个副本数量的字段，用于描述我们所需要的符合规范的 Pod 数量。&lt;/p&gt;

&lt;p&gt;有了 &lt;code&gt;ReplicaSet&lt;/code&gt;，我们就可以告诉 Kubernetes：“我需要一个叫做 &lt;code&gt;web&lt;/code&gt; 的 &lt;code&gt;ReplicaSet&lt;/code&gt;，其中包含 3 个 Pod，这些 Pod 符合如下规范：&amp;hellip;&amp;hellip;”，Kubernetes 会根据这个指令来确认，是不是刚好有三个符合规范的 Pod。如果我们从头开始，就会创建这 3 个 Pod。如果已经有了 3 个 Pod，什么事都不会发生——我们的要求和现状一致。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# pod-replicas.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: web-replicas
  labels:
    app: web
    tier: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        app: web
        tier: frontend
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;replicaset-的伸缩和高可用&#34;&gt;ReplicaSet 的伸缩和高可用&lt;/h2&gt;

&lt;p&gt;我们可以修改现存 &lt;code&gt;ReplicaSet&lt;/code&gt; 的副本数量，以此来完成伸缩。Kubernetes 会根据伸缩指令来创建或删除 Pod，让 Pod 数量符合要求。&lt;/p&gt;

&lt;p&gt;高可用方面，因为 Kubernetes 会持续的对集群进行监控，确保无论什么情况下都保有指定数量的运行实例。&lt;/p&gt;

&lt;p&gt;如果节点当机，恰好其中有一个 &lt;code&gt;web&lt;/code&gt; 所属的 Pod，Kubernetes 会另外创建一个 Pod 来替换它。如果节点没有当机，不过是有一段时间无法联系或者没有响应，那么它再次恢复可用之后，就会多出一个 Pod，Kubernetes 会中止一个 Pod 来保证数量符合要求。&lt;/p&gt;

&lt;h2 id=&#34;修改-pod-定义会发生什么&#34;&gt;修改 Pod 定义会发生什么&lt;/h2&gt;

&lt;p&gt;修改 Pod 定义并不罕见。比如我们经常会希望把容器镜像替换为新版本。&lt;/p&gt;

&lt;p&gt;记住：&lt;code&gt;ReplicaSet&lt;/code&gt; 的使命是，“确保有 N 个符合规范的 Pod。”如果我们修改了定义，会发生什么呢——突然就没有符合新规范的 Pod 了。&lt;/p&gt;

&lt;p&gt;写到这里，我们已经知道了声明式系统的工作方式：Kubernetes 会立刻创建 N 个符合新规范的 Pod。旧的 Pod 会一致存在，直到我们手工清理。&lt;/p&gt;

&lt;p&gt;如果能用 CI/CD 对这些过期 Pod 做一个自动清理可能不错；如果新 Pod 的创建能用更优雅的方式也会更好。&lt;/p&gt;

&lt;h2 id=&#34;deployment-驱动的-replicaset&#34;&gt;Deployment 驱动的 ReplicaSet&lt;/h2&gt;

&lt;p&gt;前面说的需要就是 &lt;code&gt;Deployment&lt;/code&gt; 的职责。粗看上去，&lt;code&gt;Deployment&lt;/code&gt; 的规范和 &lt;code&gt;ReplicaSet&lt;/code&gt; 很像：其中包含了 Pod 规范，以及副本数量。（还有一些后面会讨论的参数）&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# deployment-nginx.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;Deployment&lt;/code&gt; 并不会直接负责 Pod 的创建和删除。它会把这些工作委托给一个或多个 &lt;code&gt;ReplicaSet&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;在我们创建 &lt;code&gt;Deployment&lt;/code&gt; 的时候，它会用自己的 Pod 规范创建一个 &lt;code&gt;ReplicaSet&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;当更新一个 &lt;code&gt;Deployment&lt;/code&gt; 并修改副本数量时，它会把更新内容传递给下游的 &lt;code&gt;ReplicaSet&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;当配置发生了变化&#34;&gt;当配置发生了变化&lt;/h2&gt;

&lt;p&gt;需要更新 Pod 规范的时候，事情就有意思了。例如我们可能需要使用新版本的镜像（因为我们发布了新的版本），或者修改应用的参数（通过命令行参数、环境变量或者配置文件）。&lt;/p&gt;

&lt;p&gt;在我们更新 Pod 规范时，&lt;code&gt;Deployment&lt;/code&gt; 会用新的 Pod 规范创建&lt;strong&gt;新的&lt;/strong&gt; &lt;code&gt;ReplicaSet&lt;/code&gt;。新的 &lt;code&gt;ReplicaSet&lt;/code&gt; 的初始实例数量是 0。接下来 &lt;code&gt;ReplicaSet&lt;/code&gt; 的实例数量会逐步提升，同时逐渐减少另一个 &lt;code&gt;ReplicaSet&lt;/code&gt; 的尺寸。&lt;/p&gt;

&lt;p&gt;可以想象一下，面前有个混音台，我们要让新的 &lt;code&gt;ReplicaSet&lt;/code&gt; 淡入，同时把旧的那个淡出。&lt;/p&gt;

&lt;p&gt;整个过程之中，请求被发送给新旧两个 &lt;code&gt;ReplicaSet&lt;/code&gt;，用户不会感觉服务中断。&lt;/p&gt;

&lt;p&gt;全景大致如此，其中还有很多小细节，让整个过程更加健壮。&lt;/p&gt;

&lt;h2 id=&#34;损坏的-deployment-以及就绪检测&#34;&gt;损坏的 &lt;code&gt;Deployment&lt;/code&gt; 以及就绪检测&lt;/h2&gt;

&lt;p&gt;如果我们推出了一个故障版本，因为 Kubernetes 会持续把旧 Pod 替换成新的（故障）版本，它可能会让整个应用坏掉（逐个 Pod）。&lt;/p&gt;

&lt;p&gt;除非我们用上了&lt;strong&gt;就绪检测&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;就绪检测是在容器规范中加入的一个测试过程。他是一个二进制测试，结果只有两个“能行”或者“不行”，这个测试会以指定的间隔被执行（缺省情况下是每 10 秒）。&lt;/p&gt;

&lt;p&gt;Kubernetes 支持三种方式的就绪检测：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在容器内运行一个命令；&lt;/li&gt;
&lt;li&gt;向容器发出一个 HTTP(S) 请求；&lt;/li&gt;
&lt;li&gt;向容器发起一个 TCP 连接。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kubernetes 会通过测试结果来了解容器及其所处 Pod 是否准备就绪可以接受流量。在我们推出新版本时，Kubernetes 会等到新 Pod 测试得到“就绪”结果之后，才会进入下一步。&lt;/p&gt;

&lt;p&gt;如果一个 Pod 因为就绪检测持续失败，永远无法进入就绪状态，Kubernetes 也不会进入下一步。部署过程会停止，应用会继续使用老版本运行，直到我们解决了问题。&lt;/p&gt;

&lt;p&gt;如果没有就绪检测，那么这个容器成功启动后就会被当成是就绪状态。所以最好能使用就绪检测来保障业务。&lt;/p&gt;

&lt;h2 id=&#34;使用-rollback-来从故障版本中快速恢复&#34;&gt;使用 Rollback 来从故障版本中快速恢复&lt;/h2&gt;

&lt;p&gt;在滚动更新过程中或之后的任何时间，我们都可以告诉 Kubernetes：“我改主意了，请回到这个 Deployment 的前一个版本。”，这个操作会切换新旧 &lt;code&gt;ReplicaSet&lt;/code&gt; 的地位。在这个点开始，会提高旧版 &lt;code&gt;ReplicaSet&lt;/code&gt; 的实例数量到指定数值，同时降低新版的的实例数量。&lt;/p&gt;

&lt;p&gt;一般来说，并不限于新旧两个 &lt;code&gt;ReplicaSet&lt;/code&gt;。归根结底，有一个 &lt;code&gt;ReplicaSet&lt;/code&gt; 被视为“最新”版本，我们可以将这个版本作为目标 &lt;code&gt;ReplicaSet&lt;/code&gt;，所谓目标，就是我们希望运行的，也是 Kubernetes 会逐步拉起的一个版本。同时也可以有任意多个其它版本的 &lt;code&gt;ReplicaSet&lt;/code&gt;，对应旧版本。&lt;/p&gt;

&lt;p&gt;例如我们在运行 10 个副本的版本 1 应用，然后开始推出版本 2。在某个时间点，我们可能有了 7 个版本 1、3 个版本 2 的 Pod 正在运行。如果我们不想等版本 2 完全推出，决定推出版本 3。在版本 3 部署的时候，我们又想回到版本 1。整个过程，Kubernetes 都会根据需要对各个版本的 &lt;code&gt;ReplicaSet&lt;/code&gt; 中的副本数量进行调整。&lt;/p&gt;

&lt;h2 id=&#34;maxsurge-和-maxunavailable&#34;&gt;MaxSurge 和 MaxUnavailable&lt;/h2&gt;

&lt;p&gt;Kubernetes 不一定是一次更新一个 Pod 的。之前我们提到 &lt;code&gt;Deployment&lt;/code&gt; 还有一些额外的参数，这些参数中包括了 &lt;code&gt;MaxSurge&lt;/code&gt; 和 &lt;code&gt;MaxUnavailable&lt;/code&gt;，这两个参数决定了更新过程的速度。&lt;/p&gt;

&lt;p&gt;试想一下，推出新版本过程中的两个策略：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;我们可能对应用的可用性非常谨慎，因此决定在关闭旧版本 Pod 之前，首先要启动新 Pod。只有新 Pod 启动、运行并就绪之后，才终结旧 Pod。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;上这个假设中有个隐含条件就是我们的集群中是有剩余资源的。然而如果我们的集群已经满载，无法负担多余 Pod 的消耗，那么我们自然是希望首先关掉旧的，然后才启动新的。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;MaxSurge&lt;/code&gt; 指出了我们在滚动更新时，可以有多少个额外的 Pod；而 &lt;code&gt;MaxUnavailable&lt;/code&gt; 则代表在滚动更新时，我们可以忍受多少个 Pod 无法提供服务。这两个参数可以是 Pod 数量，也可以是 &lt;code&gt;Deployment&lt;/code&gt; 的实例数量百分比；两个参数都可以设置为 0（但是不能同时为 0）。&lt;/p&gt;

&lt;p&gt;接下来看看这两个参数的常见取值，以及背后的意图。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MaxUnavailable&lt;/code&gt; 设置为 0 意味着：“在新 Pod 启动并就绪之前，不要关闭任何旧 Pod”。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MaxSurge&lt;/code&gt; 设置为 100% 的意思是：“立即启动所有新 Pod”，也就是说我们有足够的资源，我们希望尽快完成更新。&lt;/p&gt;

&lt;p&gt;这两个参数的却升值都是 25%，如果我们更新一个 100 Pod 的 &lt;code&gt;Deployment&lt;/code&gt;，会立刻创建 25 个新 Old，同时会关闭 25 个旧 Pod。每次有 Pod 启动就绪，就可以关闭旧 Pod。每次有旧 Pod 完成关闭过程（释放资源），就可以创建另一个新 Pod 了。&lt;/p&gt;

&lt;h2 id=&#34;演示时间&#34;&gt;演示时间&lt;/h2&gt;

&lt;p&gt;可以很方便的观察这些参数的作用。我们不需要编写自己的 YAML、定义就绪检测等东西。&lt;/p&gt;

&lt;p&gt;我们需要做的事情只是，使用一个无效的镜像，例如一个不存在的镜像。这个容器永远无法启动，Kubernetes 也永远无法把它标记为就绪。&lt;/p&gt;

&lt;p&gt;如果你有个 Kubernewtes 集群（Minikube 或者 Docker 桌面版的单结点集群都可以），可以在不同终端运行下面的命令，来看看发生了什么：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl get pods -w
kubectl get replicasets -w
kubectl get deployments -w
kubectl get events -w
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后用下面的命令来创建、伸缩以及更新一个 &lt;code&gt;Deployment&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl run deployment web --image=nginx
kubectl scale deployment web --replicas=10
kubectl set image deployment web nginx=that-image-does-not-exist
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;会看到部署过程停顿了，但是还有 80% 的应用容量是可用的。&lt;/p&gt;

&lt;p&gt;如果我们运行 &lt;code&gt;kubectl rollout undo deployment web&lt;/code&gt;，Kubernetes 就会回滚到使用 &lt;code&gt;nginx&lt;/code&gt; 镜像的旧版本。&lt;/p&gt;

&lt;h2 id=&#34;理解选择器和标签&#34;&gt;理解选择器和标签&lt;/h2&gt;

&lt;p&gt;前面我们说过，&lt;code&gt;ReplicaSet&lt;/code&gt; 的任务是确保有 N 个符合规范的 Pod。这其实并不完全。实际上 &lt;code&gt;ReplicaSet&lt;/code&gt; 并不关心 Pod 的规范，它关心的只是标签。&lt;/p&gt;

&lt;p&gt;换句话说，不论 Pod 运行的是 &lt;code&gt;nginx&lt;/code&gt; 还是 &lt;code&gt;redis&lt;/code&gt; 还是什么别的什么东西；所有的关注点都是，它们要有正确的标签。前面的例子中，标签大概是 &lt;code&gt;run=web&lt;/code&gt; 以及 &lt;code&gt;pod-template-hash=xxxyyyzzz&lt;/code&gt; 的形式。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ReplicaSet&lt;/code&gt; 包含了一个 &lt;code&gt;selector&lt;/code&gt; 成员，内容是一个逻辑表达式，功能和 SQL 中的 &lt;code&gt;SELECT&lt;/code&gt; 类似，用来选择符合要求的 Pod。&lt;code&gt;ReplicaSet&lt;/code&gt; 保证 Pod 的数量正确，如有必要，就会新建或者删除 Pod，但是不会修改已经存在的 Pod。&lt;/p&gt;

&lt;p&gt;这样会有个设想：可能可以手工创建带有这些标签的 Pod ，但是却用的不同镜像（或者不同配置），就能骗过 &lt;code&gt;ReplicaSet&lt;/code&gt; 了。&lt;/p&gt;

&lt;p&gt;粗看上去，这可能是个很大的潜在问题。但实际上，我们很难恰巧选择了正确的标签，这是因为标签中包含了根据 Pod 规范运算得出的哈希值。&lt;/p&gt;

&lt;h2 id=&#34;service-负载均衡&#34;&gt;Service 负载均衡&lt;/h2&gt;

&lt;p&gt;选择器还用在 &lt;code&gt;Service&lt;/code&gt; 上，这个对象负责 Kubernetes 的内外部的负载均衡。我们可以给 &lt;code&gt;web&lt;/code&gt; 创建一个 &lt;code&gt;Service&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl expose deployment web --port=80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个服务会有它自己的内部 IP 地址（&lt;code&gt;ClusterIP&lt;/code&gt;），连接到这个地址的 80 端口会被负载均衡到这个 &lt;code&gt;Deployment&lt;/code&gt; 所有 Pod 之中。&lt;/p&gt;

&lt;p&gt;事实上这个连接的负载均衡范围是所有符合 &lt;code&gt;Service&lt;/code&gt; 标签选择器的 Pod 中，例如这里对应的是 &lt;code&gt;run=web&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;在我们编辑 &lt;code&gt;Deployment&lt;/code&gt; 并触发滚动时，就会创建新的 &lt;code&gt;ReplicaSet&lt;/code&gt;。这个 &lt;code&gt;ReplicaSet&lt;/code&gt; 会创建 Pod，新 Pod 标签会包含 &lt;code&gt;run=web&lt;/code&gt;，所以这些 Pod 就会自动的接到流量。&lt;/p&gt;

&lt;p&gt;这表明在滚动更新时，Deployment 不需要因为 Pod 的的启动停止，而去重新配置或者通知负载均衡器。负载均衡器通过 &lt;code&gt;selector&lt;/code&gt; 自动的完成任务。&lt;/p&gt;

&lt;p&gt;如果你好奇就绪检测的内幕：Pod 只有在所有成员容器都通过就绪检测之后才会作为有效的 &lt;code&gt;Endpoint&lt;/code&gt; 被加入服务。换句话说，Pod 只有准备就绪之后才会开始接收流量。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-部署的高级策略&#34;&gt;Kubernetes 部署的高级策略&lt;/h2&gt;

&lt;p&gt;有些事后我们希望在推出新版本时候还有更多的控制。&lt;/p&gt;

&lt;p&gt;两个知名流行技术是蓝绿部署以及金丝雀部署。&lt;/p&gt;

&lt;h3 id=&#34;kubernetes-中的蓝绿部署&#34;&gt;Kubernetes 中的蓝绿部署&lt;/h3&gt;

&lt;p&gt;在蓝绿部署中，我们希望立即把所有流量从旧版本切换到新版本，而不是象之前说的渐进切换。提出这种要求可能有几个原因：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;我们不想混合新旧请求，希望能够尽可能清晰的从旧版本切换到新版本；&lt;/li&gt;
&lt;li&gt;我们正在更新多个组件（例如 Web 前端和 API 后端），不想新版本前端和旧版后端发生联系；&lt;/li&gt;
&lt;li&gt;如果出现问题，我们希望有能力尽快回滚，无需等旧版本容器重启。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在 Kubernetes 中，可以用创建多个 &lt;code&gt;Deployment&lt;/code&gt; 的方式来完成蓝绿部署，通过对 Service 的 &lt;code&gt;Selector&lt;/code&gt; 字段的控制来进行切换。&lt;/p&gt;

&lt;p&gt;下面的命令会创建两个 &lt;code&gt;Deployment&lt;/code&gt;：&lt;code&gt;blue&lt;/code&gt; 和 &lt;code&gt;green&lt;/code&gt;，分别使用 &lt;code&gt;nginx&lt;/code&gt; 和 &lt;code&gt;httpd&lt;/code&gt; 镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl create deployment blue --image=nginx
kubectl create deployment green --image=httpd
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来我们创建一个 &lt;code&gt;Service&lt;/code&gt;，起初不会发送任何流量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl create service clusterip web --tcp=80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后我们更新 &lt;code&gt;web&lt;/code&gt; 服务的选择器：&lt;code&gt;kubectl edit service web&lt;/code&gt;。这个命令会从 Kunernetes API 中抓取服务对象的定义，在文本编辑器中打开。在其中查找：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;selector:
  app: web
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把其中的 &lt;code&gt;web&lt;/code&gt; 替换成 &lt;code&gt;blue&lt;/code&gt; 或者 &lt;code&gt;green&lt;/code&gt; 或者别的什么。保存并退出。&lt;code&gt;kubectl&lt;/code&gt; 会把更新的定义推送给 Kubernetes API，然后 &lt;code&gt;web&lt;/code&gt; 服务现在就会向特定的 &lt;code&gt;Deployment&lt;/code&gt; 发送流量了。&lt;/p&gt;

&lt;p&gt;可以用 &lt;code&gt;kubectl get svc web&lt;/code&gt; 命令获取服务的地址，并使用 &lt;code&gt;curl&lt;/code&gt; 进行访问。&lt;/p&gt;

&lt;p&gt;我们用文本编辑器作出的变更，也可以完全使用命令行来完成，例如 &lt;code&gt;kubectl patch&lt;/code&gt; 命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;kubectl patch service web -p &#39;{&amp;quot;spec&amp;quot;: {&amp;quot;selector&amp;quot;: {&amp;quot;app&amp;quot;: &amp;quot;green&amp;quot;}}}&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;蓝绿部署的好处是，流量切换几乎是立刻完成的，推出和回滚都可以很方便的通过更新 &lt;code&gt;Serevice&lt;/code&gt; 定义来完成。&lt;/p&gt;

&lt;h3 id=&#34;用-kubernetes-完成金丝雀部署&#34;&gt;用 Kubernetes 完成金丝雀部署&lt;/h3&gt;

&lt;p&gt;有时我们不想让测试版本影响所有用户，即使是短时间也不行。所以我们可以部分推出新版本。例如我们部署新旧两组实例，1% 的流量发送给新版本。&lt;/p&gt;

&lt;p&gt;接下来我们在新旧版本的监控数据中进行观察。如果情况允许，就可以向前推进；如果延迟、错误率或者其它什么东西看起来有问题，就回滚到旧版本。&lt;/p&gt;

&lt;p&gt;由于 Kubernetes 的标签和选择器的机制，可以很简单的实现这种策略。&lt;/p&gt;

&lt;p&gt;前面的例子中，我们修改了服务的选择器，接下来我们修改一下 Pod 标签。&lt;/p&gt;

&lt;p&gt;例如设置服务的选择器，让它选择带有 &lt;code&gt;status=enabled&lt;/code&gt; 的 Pod，然后给特定的 Pod 打上标签：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl label pod fronted-aabbccdd-xyz status=enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也可以一次打上多个标签：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl label pods -l app=blue,version=v1.5 status=enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;删除标签同样简单：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl label pods -l app=blue,version=v1.4 status-
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;我们看到了一些用于安全部署的技术，其中的一些能够很方便的降低因部署造成的停机时间，这让我们可以在不担心影响用户的情况下提高部署频度。&lt;/p&gt;

&lt;p&gt;有些技术给我们系上安全带，阻止问题版本影响服务。还有些别的服务让我们感觉安心。有点像主机游戏中的保存按钮——在尝试困难操作之前，我们知道如果出了问题，我们还可以回到从前。&lt;/p&gt;

&lt;p&gt;Kubernetes 让开发和运维团队能够使用这些技术来提高部署的安全性。如果部署的危险系数降低，那么就可以更频繁地、渐进地进行部署，并可以更方便的观察变更的后果。&lt;/p&gt;

&lt;p&gt;这一切都会让我们的新特性和修复特性能够更快面世，让我们的应用有更好的可用性。这也是实现容器化和持续交付的重要基础。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：Kubeseal</title>
      <link>/post/introduce-kubeseal/</link>
      <pubDate>Wed, 24 Jul 2019 21:25:21 +0800</pubDate>
      <guid>/post/introduce-kubeseal/</guid>
      <description>

&lt;p&gt;今天更新 Homebrew 的时候，眼角余光撇到一个 kube 开头的 Formula：kubeseal，名字还挺酷的，&lt;code&gt;brew home&lt;/code&gt; 看了一下&lt;a href=&#34;https://github.com/bitnami-labs/sealed-secrets&#34; target=&#34;_blank&#34;&gt;项目主页&lt;/a&gt;，还是 &lt;a href=&#34;https://bitnami.com/&#34; target=&#34;_blank&#34;&gt;bitnami&lt;/a&gt; 的作品，就多看了下，发现是一个不明觉厉的工具，本着“来都来了”的乐观精神，写了这一篇不知所云的东西（还发现了个 Issue）。&lt;/p&gt;

&lt;p&gt;（可能也许大概差不多）有一种情况，我们需要用 YAML 的形式生成一个 Secret，但是我们希望 YAML 自身的内容是加密的，以保证传输过程中，Secret 自身的内容不会被截获，但是同时这个 YAML 还能用于生成我们需要的 Secret。&lt;/p&gt;

&lt;p&gt;Kubeseal 就可以解决这个问题，它在安装时，生成一个 TLS Secret，可以用来对 Secret 进行加密，用 CRD 的方式来进行 Secret 保存，把加密的 CRD YAML 提交到集群，Kubeseal 的服务端控制器会根据 CRD 内容进行解密，生成真正的 Secret。&lt;/p&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;目前 Kubeseal 版本为 v0.8，安装很方便：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# 服务端
$ kubectl apply -f kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.8.0/controller.yaml
# 客户端（Homebrew）
$ brew install kubeseal
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;安装结束后，在 &lt;code&gt;kube-system&lt;/code&gt; 命名空间中，生成了 Kubeseal 的控制器，以及用于加解密的 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get secret,deploy -n kube-system | grep seal
secret/sealed-secrets-controller-token-v4gbx   kubernetes.io/service-account-token   3      6h26m
secret/sealed-secrets-keyb2tvx                 kubernetes.io/tls                     2      6h26m
secret/sealed-secrets-keyhjmbs                 kubernetes.io/tls                     2      21m
deployment.extensions/sealed-secrets-controller   1/1     1            1           6h26m
$ kubectl api-resources| grep seal
sealedsecrets                                    bitnami.com                    true         SealedSecret
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;创建一个-加密-的-secret&#34;&gt;创建一个“加密”的 Secret&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# 生成 Secret 的 YAML
$ kubectl create secret generic top-secret \
&amp;gt; --dry-run --from-literal=cloud=grass -o json  &amp;gt; mario.json
# 加密
$ cat mario.json | kubeseal &amp;gt; mario-secret.json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看新生成的 &lt;code&gt;mario-secret.json&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;kind&amp;quot;: &amp;quot;SealedSecret&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;bitnami.com/v1alpha1&amp;quot;,
  &amp;quot;metadata&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;top-secret&amp;quot;,
    &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;,
    &amp;quot;creationTimestamp&amp;quot;: null
  },
  &amp;quot;spec&amp;quot;: {
    &amp;quot;template&amp;quot;: {
      &amp;quot;metadata&amp;quot;: {
        &amp;quot;name&amp;quot;: &amp;quot;top-secret&amp;quot;,
        &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;,
        &amp;quot;creationTimestamp&amp;quot;: null
      }
    },
    &amp;quot;encryptedData&amp;quot;: {
      &amp;quot;cloud&amp;quot;: &amp;quot;AgA89tN49OyoDn/19+QF4Qi7w5aq5v71Xvkzu9cA6mzF/QoDInq3xWnPHl6tt93yurZC0WY+XhlLYVHss3nfrkNtdR8+GSQioTiCRiy1oXnWW3ku37eJGbe7sbd3qIm/uoR/Q3Bvg138zhYfApdeI2T1ePfjDGOsqRRhwhYY5RHAJUsbCC1H0+EO/j/Cg/DmAheFbJHgHtVnHz0eEC6JOrFtLr5YpKXEgEnDIyULoj+TtGL5VXpDzDXwf5OZNvKLgOHl80WwmJWDeyjbbE0RGoPW7rcCyOwlMe/ywRaKgJqpgEm0n/v+3Wb87kd5du8cRsFWKc1ObK4UL0Gq4FMDNXI3m1rBFkeq7AEFiyKSkqlMnPQSOslMcq10hfEmUeOqiJE/GejUlvnhyix9zit83LNlzYxgWMRav+b5CI3PbxpFxAN6r+p+wfKmAMLMh7sfGsYbj5i1vvFbztCrFlGy5UzDBV4tz58mAjDUduyHAzSHkSXHTtMo5YFMk/awonpuUIbQVtGdfyLLqQlyvkcOIdCPVFiMSbRkr0ySPFXDilQp61VtY1002bu5kM6y30fKMSBhjY3ZCes6cz8RpytVszCF8fWDcJSDfI6eGBbgKkRNtm4CPRn6QZA5etgPJxry0PyCZCAmnhfcDodeZgy+bPbR4+G1YoxGq3UaTUhsf6R/nT/rvccphnIr/VrSd+/P9XgY4kZPnw==&amp;quot;
    }
  },
  &amp;quot;status&amp;quot;: {

  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把这个文件提交到集群，并查看生成的 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# 看看是否生成了 secret
$ kubectl get secrets
NAME                  TYPE                                  DATA   AGE
...
top-secret            Opaque                                1      6s
# 查看 top-secret 内容
$ kubectl view-secret top-secret cloud
grass
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;备份-恢复和轮转&#34;&gt;备份、恢复和轮转&lt;/h2&gt;

&lt;p&gt;前面提到，Kubeseal 安装过程中除了生成 Deployment 之外，还生成了一个 Secret，仔细观察会看到这个 Secret 带有一个标签：&lt;code&gt;sealedsecrets.bitnami.com/sealed-secrets-key=active&lt;/code&gt;，代表这个 Secret 是正用于加密的。只要把这个 Secret 进行备份，或者复制到其它集群上，就可以用同样的密钥进行加密了。
如果把这个标签值修改为 &lt;code&gt;compromised&lt;/code&gt;，就代表这一密钥已经过期。&lt;/p&gt;

&lt;p&gt;对 Secret 进行上述的修改之后，可以删除控制器 Pod，以便生成并启用新的密钥，当然，过去生成的加密内容也不再有效，需要重新进行加密。&lt;/p&gt;

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

&lt;p&gt;看都看了，万一有人有用呢。。。。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>使用 Kyverno 定义 Kubernetes 策略</title>
      <link>/post/introduce-kyverno/</link>
      <pubDate>Fri, 19 Jul 2019 09:56:01 +0800</pubDate>
      <guid>/post/introduce-kyverno/</guid>
      <description>

&lt;p&gt;Kubernetes 的日常使用过程中，在对象提交给集群之前，我们会有很多机会，很多方法对资源的 Yaml 定义进行检查和处理。很多读者应该也会知道，资源提交之后，还有机会使用 Admission Controller 对资源动动手脚，这其中其实有很多可以提炼出来的标准动作，可以用统一的控制器来进行处理，&lt;a href=&#34;https://github.com/nirmata/kyverno&#34; target=&#34;_blank&#34;&gt;Kyverno&lt;/a&gt; 就是这样一个工具。有了 Kyverno 的帮助，YAML 程序员可以根据条件对资源进行筛选，符合条件的资源可以：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;验证资源：对资源定义进行检查，不符合条件的资源拒绝创建，从而保证集群资源的合规性。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;修改资源：在资源定义中进行注入，强制资源部分行为的一致性。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;生成资源：在资源创建时，同时创建相关的资源。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;安装过程是很简单的，安装清单文件位于 &lt;code&gt;https://github.com/nirmata/kyverno/raw/master/definitions/install.yaml&lt;/code&gt;，使用 kubectl 直接部署即可：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl create -f https://github.com/nirmata/kyverno/raw/master/definitions/install.yaml
customresourcedefinition.apiextensions.k8s.io/policies.kyverno.io created
namespace/kyverno created
service/kyverno-svc created
serviceaccount/kyverno-service-account created
clusterrolebinding.rbac.authorization.k8s.io/kyverno-admin created
deployment.extensions/kyverno created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有一点需要注意的是资源的类型范围，可以在主进程的命令行参数中设定不需要处理的资源类型，缺省设置为：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  containers:
    - name: kyverno
      image: nirmata/kyverno:latest
      args: [&amp;quot;--filterKind&amp;quot;,&amp;quot;Node,Event,APIService,Policy,TokenReview,SubjectAccessReview&amp;quot;]
      ports:
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;策略定义&#34;&gt;策略定义&lt;/h2&gt;

&lt;p&gt;安装完成后，就可以编写策略了，策略的规则不算复杂，具体格式可以从 &lt;code&gt;install.yaml&lt;/code&gt; 中的 CRD 定义里面推断出来。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
  name: sample-policy
spec:
  rules: # 规则数组，spec 的唯一下级
  - name: check-rule-1
    resource: # 定义选择条件，限制生效范围
      kinds: # 生效对象类型数组，必要字段
      - Deployment
      - StatefulSet
      namespace: default  # 命名空间
      name: &amp;quot;*&amp;quot; # 资源名称
      selector: # 用更加复杂一点的方式来定义选择方式
        matchLabels: # 精确匹配标签
          app: some-app
        matchExpressions: # 表达式匹配标签
          key: &amp;quot;operator&amp;quot;
          operator: In
          values:
          - v2
          - v3
    validate:
      ...
    mutate:
      ...
    generate:
      ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;resource&lt;/code&gt; 部分是固定的，而 &lt;code&gt;validate&lt;/code&gt; &lt;code&gt;mutate&lt;/code&gt; &lt;code&gt;generate&lt;/code&gt; 三个动作则各有各的结构。&lt;/p&gt;

&lt;p&gt;下面用几个例子来演示一下他的功能。&lt;/p&gt;

&lt;h2 id=&#34;验证资源-validate&#34;&gt;验证资源（validate）&lt;/h2&gt;

&lt;p&gt;定义一个限制特定命名空间下镜像地址的策略如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
  name: check-registries
spec:
  rules:
  - name: check-registries
    resource:
      kinds:
      - Deployment
      namespace: default
    validate:
      message: &amp;quot;Registry is not allowed&amp;quot;
      pattern:
        spec:
          template:
            spec:
              containers:
              - name: &amp;quot;*&amp;quot;
                image: &amp;quot;docker.io/citizenstig/*&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个策略文件中，&lt;code&gt;pattern&lt;/code&gt; 部分和我们要处理的 &lt;code&gt;deployment&lt;/code&gt; 文档结构一致，其中支持通配符，可以用它来对目标进行校验，这里我们要求 &lt;code&gt;default&lt;/code&gt; 命名空间中的 &lt;code&gt;Deplyment&lt;/code&gt; 对象，&lt;code&gt;containers&lt;/code&gt; 下的 &lt;code&gt;image&lt;/code&gt; 字段必须符合 &lt;code&gt;docker.io/citizenstig/*&lt;/code&gt; 的通配符要求。&lt;/p&gt;

&lt;p&gt;例如下面的的 Deployment 就无法创建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
        - name: httpbin
          image: citizenstig/httpbin
          imagePullPolicy: IfNotPresent
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f httpbin.yaml
Error from server: error when creating &amp;quot;httpbin.yaml&amp;quot;: admission webhook &amp;quot;nirmata.kyverno.validating-webhook&amp;quot; denied the request:
Policy check-registries failed with following rules;rulename: check-registries;Rule check-registries: Validation has failed, err Failed to validate value citizenstig/httpbin with pattern docker.io/citizenstig/*. Path: /spec/template/spec/containers/0/image/.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;但是如果我们换个命名空间就没问题了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl create ns free
kunamespace/free created
$ kubectl apply -f httpbin.yaml -n free
deployment.extensions/httpbin created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;又或者我们不用 Deployment，直接创建 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: static-httpbin
spec:
  containers:
    - name: httpbin
      image: citizenstig/httpbin
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;果然就能够创建成功了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f pod.yaml
pod/static-httpbin created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样的绕过自然是我们不想要的，但是可以改变策略，把限制做到 Pod 上：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  rules:
  - name: check-registries
    resource:
      kinds:
      - Pod
      namespace: default
    validate:
      message: &amp;quot;Registry is not allowed&amp;quot;
      pattern:
        spec:
          containers:
          - name: &amp;quot;*&amp;quot;
            image: &amp;quot;docker.io/citizenstig/*&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样更新之后，不管是 Deployment 还是静态 Pod 都无法通过了。&lt;/p&gt;

&lt;p&gt;如果使用 &lt;code&gt;kubectl edit deploy httpbin&lt;/code&gt; 把 &lt;code&gt;image&lt;/code&gt; 字段修改为 &lt;code&gt;docker.io/citizenstig/httpbin&lt;/code&gt;，就能看到  &lt;code&gt;deployment&lt;/code&gt; 能够正常工作了。&lt;/p&gt;

&lt;p&gt;或者我们可以要求所有 Pod 都必须指定 CPU 限制：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;validate:
  message: &amp;quot;resources/limits is needed.&amp;quot;
  pattern:
    spec:
      template:
        spec:
          containers:
          - resources:
              limits:
                cpu: &amp;quot;*&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个策略提交之后，上面的 Deploy 就再次无法部署了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f httpbin.yaml
Error from server: error when creating &amp;quot;httpbin.yaml&amp;quot;: admission webhook &amp;quot;nirmata.kyverno.validating-webhook&amp;quot; denied the request:
...
Path: /spec/template/spec/containers/0/resources/limits/. Expected map[string]interface {}, found &amp;lt;nil&amp;gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;修改清单，加入资源限制，即可满足条件。&lt;/p&gt;

&lt;h2 id=&#34;修改资源-mutate&#34;&gt;修改资源（mutate）&lt;/h2&gt;

&lt;p&gt;这里也可以做类似自动注入的内容，例如我们可以要求所有 &lt;code&gt;default&lt;/code&gt; 命名空间中的 Deployment，如果 &lt;code&gt;deployment&lt;/code&gt; 标签中有 &lt;code&gt;io=heavy&lt;/code&gt;，则分配到 &lt;code&gt;ssd=true&lt;/code&gt; 的节点上。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
  name: assign-ssd
spec:
  rules:
  - name: assign-ssd
    resource:
      kinds:
      - Deployment
      namespace: default
      selector:
        matchLabels:
          io: heavy
    mutate:
      overlay:
        spec:
          template:
            spec:
              nodeSelector:
                ssd: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;修改一下上面的 &lt;code&gt;Deployment&lt;/code&gt;，加上标签：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: httpbin
  labels:
    io: heavy
spec:
...
&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 deployments httpbin -o yaml
apiVersion: extensions/v1beta1
kind: Deployment
...
    spec:
...
      dnsPolicy: ClusterFirst
      nodeSelector:
        ssd: &amp;quot;true&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到多出来的 &lt;code&gt;nodeSelector&lt;/code&gt; 字段，如果查看 Pod 信息，也会发现这个 Deployment 的所有 Pod 都分配到了指定的节点上。&lt;/p&gt;

&lt;h2 id=&#34;创建资源-generate&#34;&gt;创建资源（generate）&lt;/h2&gt;

&lt;p&gt;有时候我们在 Kubernetes 上创建资源的时候，可能希望同时提供一些缺省资源，例如一个新的命名空间，我们希望其中包含缺省的 Configmap 或者 SA 或者资源限制。&lt;/p&gt;

&lt;p&gt;例如我们要在新建 &lt;code&gt;test-n&lt;/code&gt; 的命名空间的同时，创建名为 &lt;code&gt;dummy&lt;/code&gt; 的 sa。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
  name: auto-sa
spec:
  rules:
  - name: auto-sa
    resource:
      kinds:
      - Namespace
      name: &amp;quot;test-*&amp;quot;
    generate:
      kind: ServiceAccount
      name: dummy
      data:
        spec: {}
        metadata:
          labels:
            source: &amp;quot;webhook&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个策略生效后，每次我们创建形如 &lt;code&gt;test-*&lt;/code&gt; 的命名空间，其中都会生成对应的名为 &lt;code&gt;dummy&lt;/code&gt; 的 ServiceAccount，并且有标签：&lt;code&gt;source=webhook&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;Generate 还提供了复制对象的方法，例如每个新命名空间中都应该复制一个名为 &lt;code&gt;conn&lt;/code&gt; 的 &lt;code&gt;Configmap&lt;/code&gt;，就可以使用如下策略：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion : kyverno.io/v1alpha1
kind: Policy
metadata:
  name: auto-cm
spec:
  rules:
  - name: auto-cm
    resource:
      kinds:
      - Namespace
      name: &amp;quot;test-*&amp;quot;
    generate:
        kind: ConfigMap
        name: conn
        clone:
          namespace: default
          name: conn
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;随意验证一下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl create configmap conn \
    --from-literal=mysql=mysql \
    --from-literal=mongodb=mongodb
configmap/conn created
$ kubectl create ns test-6
namespace/test-6 created
$ kubectl get cm,sa -n test-6
NAME             DATA   AGE
configmap/conn   2      6s

NAME                     SECRETS   AGE
serviceaccount/default   1         7s
serviceaccount/dummy     1         6s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里会发现，随着新的命名空间的创建，新的 SA 和 CM 也都出现了。&lt;/p&gt;

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

&lt;p&gt;相对于其他的类似工具，Kyverno 在灵活、强大和易用之间取得了一个很好的平衡，不需要太多学习时间，就能够提供相当方便的功能，官网提供了大量的针对各种场景的样例，非常值得一看。&lt;/p&gt;

&lt;h2 id=&#34;参考链接&#34;&gt;参考链接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;项目主页：&lt;code&gt;https://kyverno.io/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>33 个 Kubernetes 安全工具</title>
      <link>/post/33-kubernetes-security-tools/</link>
      <pubDate>Thu, 11 Jul 2019 15:00:49 +0800</pubDate>
      <guid>/post/33-kubernetes-security-tools/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://sysdig.com/blog/33-kubernetes-security-tools/&#34; target=&#34;_blank&#34;&gt;33 Kubernetes security tools&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://sysdig.com&#34; target=&#34;_blank&#34;&gt;Mateo Burillo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;h2&gt;目录&lt;/h2&gt;
HAHAHUGOSHORTCODE-TOC0-HBHB&lt;/p&gt;

&lt;p&gt;Kubernetes 的安全工具多得很，有不同的功能、范围以及授权方式。因此我们建立了这个 Kubernetes 安全工具列表，其中有来自不同厂商的开源项目和商业平台，读者可以根据兴趣和需要进行了解和选择。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-安全工具-分类&#34;&gt;Kubernetes 安全工具——分类&lt;/h2&gt;

&lt;p&gt;为了方便读者浏览目录，我们把这些工具按照主要功能和范围进行了分类：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 镜像扫描和静态分析&lt;/li&gt;
&lt;li&gt;Kubernetes 运行时安全&lt;/li&gt;
&lt;li&gt;Kubernetes 网络安全&lt;/li&gt;
&lt;li&gt;镜像分发和机密管理&lt;/li&gt;
&lt;li&gt;Kubernetes 安全审计&lt;/li&gt;
&lt;li&gt;端到端的 Kubernetes 安全商业产品&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们最爱的容器编排平台已经成熟，会有越来越多的 Kubernetes 安全工具涌现出来，如果读者发现我们列表的错漏，请在 Twitter 上联系 &lt;a href=&#34;https://twitter.com/sysdig&#34; target=&#34;_blank&#34;&gt;@sysdig&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;言归正传。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-镜像扫描&#34;&gt;Kubernetes 镜像扫描&lt;/h2&gt;

&lt;h3 id=&#34;anchore&#34;&gt;Anchore&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/anchore_k8s_security.png&#34; alt=&#34;anchore&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://anchore.com&#34; target=&#34;_blank&#34;&gt;https://anchore.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）以及商业产品&lt;/p&gt;

&lt;p&gt;Anchore 引擎不但能够对容器镜像进行分析，更可以使用用户自定义的策略来完成自定义的安全检查。&lt;/p&gt;

&lt;p&gt;除了利用 CVE 数据库来对已知威胁进行扫描之外，Anchore 还提供了很多附加标准可以进行配置，来作为扫描策略的一部分：Dockerfile 检查、凭据泄露、语言相关内容（mpm、maven 等）、软件许可等。&lt;/p&gt;

&lt;h3 id=&#34;clair&#34;&gt;Clair&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/clair_k8s_security.png&#34; alt=&#34;Clair&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://coreos.com/clair&#34; target=&#34;_blank&#34;&gt;https://coreos.com/clair&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Clair 是最早开源的镜像扫描项目之一，也是 Quay 镜像库的安全扫描引擎。Clair 能从很多数据源中拉取 CVE 信息，其中包括来自 Debian、RedHat 或者 Ubuntu 安全团队的特定发行版的威胁列表。&lt;/p&gt;

&lt;p&gt;和 Anchore 不同的是，Clair 专注于威胁检测和 CVE 匹配的功能，也提供了一定的扩展性，让用户通过实现可插接驱动来实现扩展。&lt;/p&gt;

&lt;h3 id=&#34;dagda&#34;&gt;Dagda&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/dagda_k8s_security.png&#34; alt=&#34;Dagda&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://github.com/eliasgranderubio/dagda&#34; target=&#34;_blank&#34;&gt;https://github.com/eliasgranderubio/dagda&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Dagda 会针对容器镜像中已知的漏洞、特洛伊、病毒、恶意软件和其它恶意威胁进行静态分析。&lt;/p&gt;

&lt;p&gt;和其它的 Kubernetes 安全工具相比，Dagda 有两个与众不同之处：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;原生集成了 &lt;a href=&#34;https://www.clamav.net/&#34; target=&#34;_blank&#34;&gt;ClamAV&lt;/a&gt;，不仅可以扫描镜像，还能用作防毒软件。&lt;/li&gt;
&lt;li&gt;Dagda 还提供了运行时保护功能。从 Docker 守护进程实时收集事件，并和 Falco 集成识别安全事件。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;kubexray&#34;&gt;KubeXray&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/kubexray_k8s_security.png&#34; alt=&#34;KubeXray&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://github.com/jfrog/kubexray&#34; target=&#34;_blank&#34;&gt;https://github.com/jfrog/kubexray&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache），但是需要从 JFrog Xray（商业产品）获取数据。&lt;/p&gt;

&lt;p&gt;KubeXray 监听 Kubernetes API Server 的事件，并利用 JFrog Xray（商业产品）的元数据来确认只有符合策略要求的 Pod 才能运行。&lt;/p&gt;

&lt;p&gt;KubeXray 不只会对新建或者更新的容器部署进行审计（Kuberentes 准入控制就是这样），还能动态的根据新的安全策略对运行中的容器进行检查，并删除有漏洞的镜像所对应的资源。&lt;/p&gt;

&lt;h3 id=&#34;snyk&#34;&gt;Snyk&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/snyk_k8s_security.png&#34; alt=&#34;Snyk&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://snyk.io/&#34; target=&#34;_blank&#34;&gt;https://snyk.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）以及商业产品&lt;/p&gt;

&lt;p&gt;Snyk 是一个特别的漏洞检测工具，其特点是着眼于开发工作流，自称是开发第一的解决方案。&lt;/p&gt;

&lt;p&gt;Snyk 会直接链接到代码仓库，解析项目结构，并分析引入的代码及其直接和间接依赖。Snyk 支持很多流行的编程语言，还能发现潜在的许可风险。&lt;/p&gt;

&lt;h3 id=&#34;trivy&#34;&gt;Trivy&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/trivy_k8s_security.png&#34; alt=&#34;Trivy&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://github.com/knqyf263/trivy&#34; target=&#34;_blank&#34;&gt;https://github.com/knqyf263/trivy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（AGPL）&lt;/p&gt;

&lt;p&gt;Trivy 是个简单全面的容器漏洞检测工具，能够方便的和 CI/CD 进行集成。它的安装和操作都很简单，只需要一个二进制文件，无需安装数据库和其它的附加内容。&lt;/p&gt;

&lt;p&gt;Trivy 的简便性的一个缺点是需要学习如何解析和转发它的 JSON 输出，这才能方便其它工具进行调用。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-运行时安全&#34;&gt;Kubernetes 运行时安全&lt;/h2&gt;

&lt;h3 id=&#34;falco&#34;&gt;Falco&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/falco_k8s_security.png&#34; alt=&#34;Falco&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://falco.org/&#34; target=&#34;_blank&#34;&gt;https://falco.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Falco 是一个云原生的运行时安全工具，CNCF 成员项目。&lt;/p&gt;

&lt;p&gt;利用 Sysdig 的 Linux 内核指令和系统调用分析，Falco 能够深入理解系统行为。它的运行时规则引擎能够检测应用、容器、主机以及 Kubernetes 的反常行为。&lt;/p&gt;

&lt;p&gt;凭借 Falco，在每个 Kubernetes 节点部署一个代理，无需修改或者注入第三方代码或者加入 Sidecar 容器，就能够得到完整的运行时可见性以及威胁检测。&lt;/p&gt;

&lt;h3 id=&#34;linux-运行时安全框架&#34;&gt;Linux 运行时安全框架&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/linux_security_tools.png&#34; alt=&#34;Linux runtime security frameworks&#34; /&gt;&lt;/p&gt;

&lt;p&gt;原生的 Linux 框架其实不能算作是“Kubernetes 安全工具”，但它们的运行时安全上下文是可以包括在 Kubernetes 的 Pod 安全策略之中的（PSP），所以还是值得一提。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://gitlab.com/apparmor/apparmor/wikis/home/&#34; target=&#34;_blank&#34;&gt;AppArmor&lt;/a&gt; 为容器内的进程附加一个安全档案，其中定义了文件系统、权限、网络访问规则、库链接等。这是一个访问控制系统，会阻止未经授权的动作发生。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://selinuxproject.org/page/Main_Page&#34; target=&#34;_blank&#34;&gt;SELinux&lt;/a&gt; 是一个 Linux 内核安全模块，和 AppArmor 有点相似，常常被拉来做比较。SELinux 更加强大，粒度更细，也比 AppArmor 更有弹性，学习曲线更加陡峭、也更加复杂。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/seccomp/libseccomp&#34; target=&#34;_blank&#34;&gt;Seccomp&lt;/a&gt; 和 seccomp-bpf 允许对系统调用进行过滤，可以防止用户的二进制文对主机操作系统件执行通常情况下并不需要的危险操作。它和 &lt;a href=&#34;https://sysdig.com/blog/selinux-seccomp-falco-technical-discussion/&#34; target=&#34;_blank&#34;&gt;Falco 有些类似&lt;/a&gt;，不过 Seccomp 没有为容器提供特别的支持。&lt;/p&gt;

&lt;h3 id=&#34;开源版-sysdig&#34;&gt;开源版 Sysdig&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/sysdig_k8s_security.png&#34; alt=&#34;Sysdig open source&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.sysdig.com/opensource&#34; target=&#34;_blank&#34;&gt;https://www.sysdig.com/opensource&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Sysdig 是一个全面的 Linux 系统（在 Windows 和 Mac OSX 下也提供了有限支持）下的观察、排错和调试工具。可以用来对主机操作系统以及运行其上的容器进行详细的监控和观察。&lt;/p&gt;

&lt;p&gt;Sysdig 还对容器运行时以及 Kubernetes 元数据提供了原生支持，能在收集到的系统活动数据中加入额外的维度和标签。Sysdig 提供了很多方式来探索 Kubernetes 集群：可以使用 &lt;a href=&#34;https://github.com/sysdiglabs/kubectl-capture&#34; target=&#34;_blank&#34;&gt;kubectl capture&lt;/a&gt; 创建一个是检点的快照，或者使用 &lt;a href=&#34;https://github.com/sysdiglabs/kubectl-dig&#34; target=&#34;_blank&#34;&gt;kubectl dig&lt;/a&gt; 来进行交互访问。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-网络安全&#34;&gt;Kubernetes 网络安全&lt;/h2&gt;

&lt;h3 id=&#34;aporeto&#34;&gt;Aporeto&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/aporeto_k8s_security.png&#34; alt=&#34;aporeto&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.aporeto.com/&#34; target=&#34;_blank&#34;&gt;https://www.aporeto.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;Aporeto 提供了“从网络和基础设施中解耦的安全性”。这意味着你的 Kubernetes 服务不只是获得了一个本地 ID（也就是 Kubernetes ServiceAccount），还有一个全局 ID/指纹，可以以此为基础和任何其它服务进行安全和双向校验的通信。&lt;/p&gt;

&lt;p&gt;Aporeto 生成的唯一身份，不仅可以提供给 Kubernetes 或者容器，还能提供给主机、云函数和用户使用，根据这些身份和网络安全策略的配置，可以选择性的对通信进行放行或者阻断。&lt;/p&gt;

&lt;h3 id=&#34;calico&#34;&gt;Calico&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/calico_k8s_security.png&#34; alt=&#34;Calico&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.projectcalico.org/&#34; target=&#34;_blank&#34;&gt;https://www.projectcalico.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Calico 经常随容器编排系统一同部署，用于实现容器之间的虚拟网络。在基础的网络功能之外，Calico 项目还实现了 Kubernetes 网络策略规范，以及自己的一套安全策略，其中包括了端点的 ACL 和基于注解的入栈/出栈网络安全规则。&lt;/p&gt;

&lt;h3 id=&#34;cilium&#34;&gt;Cilium&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/cilium_k8s_security.png&#34; alt=&#34;cilium&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.cilium.io/&#34; target=&#34;_blank&#34;&gt;https://www.cilium.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Cilium 提供了容器防火墙以及网络安全功能，适用于 Kubernetes 和微服务负载。Cilium 依赖于一种新的 Linux 内核技术——BPF，用它来执行核心数据路径的过滤、监控、重整、重定向等功能。&lt;/p&gt;

&lt;p&gt;Cilium 能够根据容器身份（Docker 或者 Kubernetes 标签和元数据）进行网络访问策略的定义。Cilium 还能理解并过滤多种 HTTP、gRPC 这样的 L7 协议（例如可以设置两个 Kubernetes 部署之间 REST API 的访问性）。&lt;/p&gt;

&lt;h3 id=&#34;istio&#34;&gt;Istio&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/istio_k8s_security.png&#34; alt=&#34;Istio&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://istio.io/&#34; target=&#34;_blank&#34;&gt;https://istio.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Istio 是广为人知的服务网格产品，它通过部署平台无关的控制平面，并把所有托管服务流量重新路由到动态配置的 Envoy Proxy 上完成网格功能。Istio 占据了通信的主动权，能够为微服务和容器实现多种网络安全策略。&lt;/p&gt;

&lt;p&gt;Istio 网络安全能力包括：透明的 TLS 加密，能够自动把微服务通信升级为 HTTPS，并且它具备 RBAC 以及鉴权能力，可以在集群中不同工作负载之间进行通信时进行接受或者拒绝的决策。&lt;/p&gt;

&lt;h3 id=&#34;tigera&#34;&gt;Tigera&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/tigera_k8s_security.png&#34; alt=&#34;Tigera&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.tigera.io/&#34; target=&#34;_blank&#34;&gt;https://www.tigera.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;Tigera 的“Kubernetes 防火墙”技术，以零信任网络的理念来加固 Kubernetes 的网络安全。&lt;/p&gt;

&lt;p&gt;与其它 Kubernetes 原生网络解决方案类似，Tigera 利用 Kubernetes 元数据来识别集群中的不同服务和实体，提供跨多云或混合单体-容器基础设施运行时检测、持续合规性检查和网络监控能力。&lt;/p&gt;

&lt;h3 id=&#34;trireme&#34;&gt;Trireme&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/trireme_k8s_security.png&#34; alt=&#34;Trireme&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.aporeto.com/opensource/&#34; target=&#34;_blank&#34;&gt;https://www.aporeto.com/opensource/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Trireme 是一个简单直接的 Kubernetes 网络策略规范的实现。它有一个与众不同的特点：无需一个中心控制平面来对网格进行协调，因此这个方案具备很好的伸缩能力。Trireme 通过在每个节点上安装代理的方式来影响主机的 TCP/IP 网络栈。&lt;/p&gt;

&lt;h2 id=&#34;镜像分发和机密管理&#34;&gt;镜像分发和机密管理&lt;/h2&gt;

&lt;h3 id=&#34;grafeas&#34;&gt;Grafeas&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/grafeas_k8s_security.png&#34; alt=&#34;Grafeas&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://grafeas.io/&#34; target=&#34;_blank&#34;&gt;https://grafeas.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Grafeas 是一个开源的 API，用于对软件供应链进行审计和监管。泛泛而论，Grafeas 是一个元数据和审计日志收集工具，可以用来跟踪组织中的安全合规实践。&lt;/p&gt;

&lt;p&gt;这种集中起来的信息可以帮助用户回答类似这样的安全问题：&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;/ul&gt;

&lt;h3 id=&#34;portieris&#34;&gt;Portieris&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/portieris_k8s_security.png&#34; alt=&#34;Portieris&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://github.com/IBM/portieris&#34; target=&#34;_blank&#34;&gt;https://github.com/IBM/portieris&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Portieris 是一个 Kubernetes 准入控制器，可以用于内容信任。它依赖 &lt;a href=&#34;https://github.com/theupdateframework/notary&#34; target=&#34;_blank&#34;&gt;Notary&lt;/a&gt; 服务器以此作为信任源头并签署工件。&lt;/p&gt;

&lt;p&gt;一旦修改了 Kubernetes 的工作负载，Portieris 就会为请求的容器镜像拉取签名信息和内容信任策略，如果需要的话，还可以修改 API 对象的内容，并以签署版本的镜像来进行替换。&lt;/p&gt;

&lt;h3 id=&#34;vault&#34;&gt;Vault&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/vault_k8s_security.png&#34; alt=&#34;Vault&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.vaultproject.io/&#34; target=&#34;_blank&#34;&gt;https://www.vaultproject.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（MPL）&lt;/p&gt;

&lt;p&gt;Vault 是一个用于存储机密（例如密码、Token、Secret 等）的高度安全的存储方案，它支持很多高级功能，例如临时安全令牌或者受编排的密钥翻转。&lt;/p&gt;

&lt;p&gt;可以用 Helm 在 Kubernetes 集群中部署 Vault 的 Chart，使用 Consul 作为存储后端。它支持 Kubernetes 的本地资源，比如说 ServiceAccount Token，甚至还能作为缺省的 Kubernetes Secret 仓库。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-安全审计&#34;&gt;Kubernetes 安全审计&lt;/h2&gt;

&lt;h3 id=&#34;kube-bench&#34;&gt;Kube-bench&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/kube-bench_k8s_security.png&#34; alt=&#34;Kube-Bench&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://github.com/aquasecurity/kube-bench&#34; target=&#34;_blank&#34;&gt;https://github.com/aquasecurity/kube-bench&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Kube-Bench 是一个 Go 应用，它会运行 &lt;a href=&#34;https://sysdig.com/products/secure/container-compliance/&#34; target=&#34;_blank&#34;&gt;CIS Kubernetes 基准测试&lt;/a&gt;中的测试，来检查 Kubernetes 部署的安全程度。&lt;/p&gt;

&lt;p&gt;Kube-Bench 会扫描你的 Kubernetes 集群组件（ETCD、API、Controller Manager 等）、敏感文件授权、不安全的帐号或者开放端口、资源配额、API 速率限制等方面查找不安全的配置参数。&lt;/p&gt;

&lt;h3 id=&#34;kube-hunter&#34;&gt;Kube-Hunter&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/kube-hunter_k8s_security.png&#34; alt=&#34;Kube Hunter&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://github.com/aquasecurity/kube-hunter&#34; target=&#34;_blank&#34;&gt;https://github.com/aquasecurity/kube-hunter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Kube-Hunter 在 Kubernetes 集群中查找安全弱点（例如远程代码执行或者信息泄露）。可以把 Kube-Hunter 作为一个远程扫描器，来从外部攻击者的视角来观察你的集群；也可以用 Pod 的方式来运行。&lt;/p&gt;

&lt;p&gt;Kube-Hunter 有个特别之处就是“active hunting”，它不仅会报告问题，而且还会尝试利用在 Kubernetes 集群中发现的问题，这种操作可能对集群有害，应小心使用。&lt;/p&gt;

&lt;h3 id=&#34;kubeaudit&#34;&gt;Kubeaudit&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/kubeaudit_k8s_security.png&#34; alt=&#34;Kube Audit&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://github.com/Shopify/kubeaudit&#34; target=&#34;_blank&#34;&gt;https://github.com/Shopify/kubeaudit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（MIT）&lt;/p&gt;

&lt;p&gt;Kubeaudit 是一个免费的命令行工具，由 Shopify 提供，用于对 Kubernetes 的配置进行多方面的审计。其中包含无限制的镜像使用、以 Root 身份运行、特权运行以及缺省的 ServiceAccount 等。&lt;/p&gt;

&lt;p&gt;Kubeaudit 由很多其它有趣的功能，例如扫描处理本地的 YAML 来查找配置缺陷和安全问题，并且自动修复。&lt;/p&gt;

&lt;h3 id=&#34;kubesec&#34;&gt;Kubesec&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/kubesec_k8s_security.png&#34; alt=&#34;Kube Sec&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://kubesec.io/&#34; target=&#34;_blank&#34;&gt;https://kubesec.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;Kubesec 是一个比较特别的 Kubernetes 安全工具，它会直接对 YAML 进行扫描，查找其中描述的 Kubernetes 资源是否使用了较弱的安全参数。&lt;/p&gt;

&lt;p&gt;例如它可以检测到 Pod 的过高权限，使用 root 作为默认用户、附加到主机网络命名空间、危险的加载操作（例如 &lt;code&gt;/proc&lt;/code&gt; 或者 Docker Socket）。它还提供了一个在线的演示，可以在上面提交 YAML 体验这一功能。&lt;/p&gt;

&lt;h3 id=&#34;open-policy-agent&#34;&gt;Open Policy Agent&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/opa_k8s_security.png&#34; alt=&#34;OPA&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.openpolicyagent.org/&#34; target=&#34;_blank&#34;&gt;https://www.openpolicyagent.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：免费（Apache）&lt;/p&gt;

&lt;p&gt;OPA 的目标是把安全策略和最佳实践从特定的运行时平台（Docker、Kubernetes、Mesosphere、Openshift 等）中解耦出来。&lt;/p&gt;

&lt;p&gt;例如可以把 OPA 作为 Kubernetes 准入控制器后端进行部署，这样 OPA 代理就可以接管安全决策，根据自定义安全约束，对请求进行校验、拒绝甚至是就地修改。OPA 使用一种自有的 DSL（Rego）编写策略。&lt;/p&gt;

&lt;h2 id=&#34;端到端的-kubernetes-安全商业产品&#34;&gt;端到端的 Kubernetes 安全商业产品&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;th&gt;运行时安全&lt;/th&gt;
&lt;th&gt;网络安全&lt;/th&gt;
&lt;th&gt;Forensics&lt;/th&gt;
&lt;th&gt;Kubernetes 审计&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AquaSec&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Capsule8&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Caviring&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Google SCC&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;插件&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Layered Insight&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;NeuVector&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;StackRox&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Sysdig Secure&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Tenable Container security&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Twistlock&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&#34;aqua-security&#34;&gt;Aqua Security&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/aquasec_k8s_security.png&#34; alt=&#34;Aqua Sec&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.aquasec.com/&#34; target=&#34;_blank&#34;&gt;https://www.aquasec.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;AquaSec 是一个针对容器和云负载的商业安全工具，包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;能够集成到容器仓库或者 CICD 的镜像扫描。&lt;/li&gt;
&lt;li&gt;能够检测容器修改或异常行为的运行时保护。&lt;/li&gt;
&lt;li&gt;容器原生的应用程序防火墙。&lt;/li&gt;
&lt;li&gt;针对云服务的 Serverless 安全。&lt;/li&gt;
&lt;li&gt;集成到事件日志的合规和审计报告。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;capsule-8&#34;&gt;Capsule 8&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/capsule8_k8s_security.png&#34; alt=&#34;capsule8_k8s_security.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://capsule8.com/&#34; target=&#34;_blank&#34;&gt;https://capsule8.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;Capsule 8 在你的自建或云端 Kubernetes 集群中部署探针，从而集成到基础设施之中。这个探针会搜集主机和网络指标，通过这些数据和攻击行为模式进行匹配。&lt;/p&gt;

&lt;p&gt;Capsule 8 团队负责在 0 day 攻击到达你的集群之前进行检测和阻止。他们的安全团队能够将安全规则推送到探针上，从而阻止软件威胁。&lt;/p&gt;

&lt;h3 id=&#34;cavirin&#34;&gt;Cavirin&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/cavirin_k8s_security.png&#34; alt=&#34;Cavirin&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.cavirin.com/&#34; target=&#34;_blank&#34;&gt;https://www.cavirin.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;Cavirin 专注于为不同的安全标准化机构提供企业版本。它的镜像扫描功能，还可以与 CI/CD 管道进行集成，在将不合规的镜像推送到镜像库之前阻止它们。&lt;/p&gt;

&lt;p&gt;Cavirin 安全套件使用机器学习为网络安全状态提供类似信用的评分，提供补救技巧，以改善安全状况或安全标准合规性。&lt;/p&gt;

&lt;h3 id=&#34;google-cloud-security-command-center&#34;&gt;Google Cloud Security Command Center&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/googlescc_k8s_security.png&#34; alt=&#34;GCP SCC&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://cloud.google.com/security-command-center/&#34; target=&#34;_blank&#34;&gt;https://cloud.google.com/security-command-center/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;Google SCC 能帮安全团队收集数据、识别威胁并在业务损失之前对其采取行动。&lt;/p&gt;

&lt;p&gt;SCC 是一个统一的控制面板，在这里可以集成不同的安全报告、资产清单以及第三方安全引擎。&lt;/p&gt;

&lt;p&gt;SCC 提供的 API 可以集成来自不同来源（&lt;a href=&#34;https://sysdig.com/blog/kubernetes-security-for-google-cloud-security-command-center/&#34; target=&#34;_blank&#34;&gt;Sysdig Secure&lt;/a&gt; 或者 &lt;a href=&#34;https://sysdig.com/blog/falco-gke-kubernetes-security/&#34; target=&#34;_blank&#34;&gt;Falco&lt;/a&gt;）的 Kubernetes 安全事件。&lt;/p&gt;

&lt;h3 id=&#34;layered-insight-qualys&#34;&gt;Layered Insight (Qualys)&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;layered_k8s_security.png&#34; alt=&#34;Layered&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://layeredinsight.com/&#34; target=&#34;_blank&#34;&gt;https://layeredinsight.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;Layered Insight（现在是 Qualys 的一部分）是围绕“嵌入式安全性”的概念设计的。它用静态分析技术扫描原有镜像漏洞并通过 CVE 检查后，Layered Insight 会注入一个二进制代理，生成一个中间镜像。&lt;/p&gt;

&lt;p&gt;这个二进制代理包括容器网络流量、I/O 流以及应用程序活动的运行时安全性探测，还包括基础架构运营商或 DevOps 团队提供的自定义安全检查内容。&lt;/p&gt;

&lt;h3 id=&#34;neuverctor&#34;&gt;Neuverctor&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/neuvector_k8s_security.png&#34; alt=&#34;neuvector_k8s_security.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://neuvector.com/&#34; target=&#34;_blank&#34;&gt;https://neuvector.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;NeuVector 通过分析网络活动和应用程序行为，为每个映像创建定制的安全配置文件，来执行容器安全基准和运行时保护。它还可以主动阻止威胁，通过修改本地网络防火墙来隔离可疑活动。&lt;/p&gt;

&lt;p&gt;NeuVector 的网络集成，标记为“安全网格”，能够对服务网格中的所有网络连接执行数据包深度检查和 L7 过滤。&lt;/p&gt;

&lt;h3 id=&#34;stackrox&#34;&gt;StackRox&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/stackrox_k8s_security.png&#34; alt=&#34;StackRox&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.stackrox.com/&#34; target=&#34;_blank&#34;&gt;https://www.stackrox.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;StackRox 容器安全平台的设计目标是涵盖 Kubernetes 集群中应用程序的整个生命周期。与此列表中的其它商业方案一样，它会根据观察到的容器行为生成运行时配置文件，并会在发现异常情况时自动发出警报。&lt;/p&gt;

&lt;p&gt;StackRox 平台还将使用 CIS Kubernetes 基准以及其他容器合规性基准，对 Kubernetes 配置进行评估。&lt;/p&gt;

&lt;h3 id=&#34;sysdig-secure&#34;&gt;Sysdig Secure&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/sysdig_secure_k8s_security.png&#34; alt=&#34;Sysdig Secure&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://sysdig.com/products/secure/&#34; target=&#34;_blank&#34;&gt;https://sysdig.com/products/secure/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;Sysdig Secure 在整个容器生命周期内对云原生应用程序实施保护。它把镜像扫描，运行时保护和取证结合在一起，以识别漏洞、阻止威胁，执行合规性并对微服务中的活动进行审计。&lt;/p&gt;

&lt;p&gt;一些重要功能包括：&lt;/p&gt;

&lt;p&gt;Scanning images in a registry or as part of the CI/CD process to uncover vulnerable libraries, packages, and configuration
Run-time detection to protect containers in production with behavioral profiles
Record pre- and post-attack activity through system calls with microsecond level granularity
250+ out of the box compliance checks to keep your configuration secure&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在镜像库中，或作为 CI/CD 过程的一部分对镜像进行扫描，以发现易受攻击的库、包和配置内容。&lt;/li&gt;
&lt;li&gt;运行时检测，使用行为配置文件来保护生产中的容器。&lt;/li&gt;
&lt;li&gt;通过系统调用，在毫秒一级对攻击前后的行为进行记录。&lt;/li&gt;
&lt;li&gt;开箱即用的超过 250 项合规性检查，帮助用户保持配置安全。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;tenable-container-security&#34;&gt;Tenable Container Security&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/tenable_k8s_security.png&#34; alt=&#34;Tenable&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.tenable.com/products/tenable-io/container-security&#34; target=&#34;_blank&#34;&gt;https://www.tenable.com/products/tenable-io/container-security&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;在容器问世之前，Tenable 在安全行业广为人知，它的 Nusus 是一款流行的漏洞扫描和安全审计工具。&lt;/p&gt;

&lt;p&gt;Tenable Container security 利用他们在计算机安全领域的经验，将 CI/CD 与漏洞数据库、专门的恶意软件检测引擎和安全威胁补救建议集成在一起。&lt;/p&gt;

&lt;h3 id=&#34;twistlock-palo-alto-networks&#34;&gt;Twistlock (Palo Alto Networks)&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/twistlock_k8s_security.png&#34; alt=&#34;Twistlock&#34; /&gt;&lt;/p&gt;

&lt;p&gt;主页：&lt;a href=&#34;https://www.twistlock.com/&#34; target=&#34;_blank&#34;&gt;https://www.twistlock.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;许可：商业&lt;/p&gt;

&lt;p&gt;Twistlock 自诩为云优先的、容器优先的平台，提供与云提供商（AWS，Azure，GCP）、容器编排器（Kubernetes，Mesospehere，Openshift，Docker），Serverless 运行时，网格框架和 CI/CD 工具的特定集成。&lt;/p&gt;

&lt;p&gt;除了通常的容器安全企业功能，如 CI/CD 管道集成或镜像扫描，Twistlock 使用机器学习技术来生成行为模式和容器感知网络规则。&lt;/p&gt;

&lt;p&gt;Twistlock 被 Palo Alto Networks 收购，Palo Alto Networks 也是 Evident.io 和 Redlock 安全解决方案的所有者。期待这三个平台合而为一，整合到 Palo Alto 的 PRISMA 中。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：Kubeval</title>
      <link>/post/intro-kubeval/</link>
      <pubDate>Mon, 10 Jun 2019 11:46:24 +0800</pubDate>
      <guid>/post/intro-kubeval/</guid>
      <description>

&lt;p&gt;这次要介绍的是一个用于验证 Kubernetes 对象文档结构的小工具：&lt;a href=&#34;https://github.com/instrumenta/kubeval&#34; target=&#34;_blank&#34;&gt;kubeval&lt;/a&gt;。它能够对一个或多个 Kubernetes 或 OpenShift 的对象文档进行校验。它所依赖的 Schema 数据来自于 Open API 所生成的内容，所以校验过程中无需和 API Server 进行交互，也正因如此，对各种第三方 CRD 的支持也暂时无能为力，另外还需要有 Schema 库的支持。&lt;/p&gt;

&lt;p&gt;这个工具在直接和 Kubernetes 交互的环境中有点画蛇添足，但是如果是在 CICD Pipeline 中，或者是没有 Kubernetes 集群可用的情况下，就很趁手了。&lt;/p&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;h3 id=&#34;linux&#34;&gt;Linux&lt;/h3&gt;

&lt;p&gt;直接下载安装版即可。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;wget https://github.com/instrumenta/kubeval/releases/download/0.9.2/kubeval-linux-amd64.tar.gz
tar xf kubeval-linux-amd64.tar.gz
sudo cp kubeval /usr/local/bin
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;macos&#34;&gt;MacOS&lt;/h3&gt;

&lt;p&gt;可以下载安装：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;wget https://github.com/instrumenta/kubeval/releases/download/0.9.2/kubeval-darwin-amd64.tar.gz
tar xf kubeval-darwin-amd64.tar.gz
sudo cp kubeval /usr/local/bin
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也可以使用 Homebrew：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;brew tap instrumenta/instrumenta
brew install kubeval
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;windows&#34;&gt;Windows&lt;/h3&gt;

&lt;p&gt;除了在 Release 页面下载 zip 包之外，还可以用 &lt;a href=&#34;https://scoop.sh/&#34; target=&#34;_blank&#34;&gt;scoop&lt;/a&gt; 进行安装：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;scoop bucket add instrumenta https://github.com/instrumenta/bucket-instrumenta
scoop install kubeval
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;docker&#34;&gt;Docker&lt;/h3&gt;

&lt;p&gt;还提供了 Docker 的运行方式，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker run -it -v `pwd`/fixtures:/fixtures garethr/kubeval fixtures/*
Missing a kind key in /fixtures/blank.yaml
The document fixtures/int_or_string.yaml contains a valid Service
The document fixtures/int_or_string_false.yaml contains an invalid Deployment
--&amp;gt; spec.template.spec.containers.0.env.0.value: Invalid type. Expected: string, given: integer
The document fixtures/invalid.yaml contains an invalid ReplicationController
--&amp;gt; spec.replicas: Invalid type. Expected: integer, given: string
Missing a kind key in /fixtures/missing-kind.yaml
The document fixtures/valid.json contains a valid Deployment
The document fixtures/valid.yaml contains a valid ReplicationController
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;使用&#34;&gt;使用&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubeval --help
Validate a Kubernetes YAML file against the relevant schema

Usage:
  kubeval &amp;lt;file&amp;gt; [file...] [flags]

Flags:
  -f, --filename string             filename to be displayed when testing manifests read from stdin (default &amp;quot;stdin&amp;quot;)
  -h, --help                        help for kubeval
  -v, --kubernetes-version string   Version of Kubernetes to validate against (default &amp;quot;master&amp;quot;)
      --openshift                   Use OpenShift schemas instead of upstream Kubernetes
      --schema-location string      Base URL used to download schemas. Can also be specified with the environment variable KUBEVAL_SCHEMA_LOCATION (default &amp;quot;https://kubernetesjsonschema.dev&amp;quot;)
      --strict                      Disallow additional properties not in schema
      --version                     version for kubeval
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;参数很少，但是都比较有用：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-v&lt;/code&gt;：指定 Kubernetes 版本，例如 &lt;code&gt;1.14.2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--openshift&lt;/code&gt;：对 Openshift 对象进行校验。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--schema-location&lt;/code&gt;：指定 Schema 库的位置，缺省为 &lt;code&gt;https://kubernetesjsonschema.dev&lt;/code&gt;，可以使用环境变量 &lt;code&gt;KUBEVAL_SCHEMA_LOCATION&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--strict&lt;/code&gt;：禁止出现 Schema 中未规定的字段。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubeval sleep.yaml
The document sleep.yaml contains a valid ServiceAccount
The document sleep.yaml contains a valid Service
The document sleep.yaml contains a valid Deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;或者也可以使用管道：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cat sleep.yaml|kubeval
The document stdin contains a valid ServiceAccount
The document stdin contains a valid Service
The document stdin contains a valid Deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;指定版本进行校验：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubeval -v 1.10.0 sleep.yaml
The document sleep.yaml contains a valid ServiceAccount
The document sleep.yaml contains a valid Service
The document sleep.yaml contains a valid Deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;使用自己的-schema-库&#34;&gt;使用自己的 Schema 库&lt;/h2&gt;

&lt;p&gt;前面提到过一个参数：&lt;code&gt;--schema-location&lt;/code&gt;，推测应该是用于在无法访问缺省模式库的时候的替补方案，要建立自己的模式库也很方便，只要使用 git 获取在线内容，并使用 Web Server 或者本地文件系统提供服务就可以了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cd /doc
$ git clone https://github.com/instrumenta/kubernetes-json-schema
正克隆到 &#39;kubernetes-json-schema&#39;...
...
$ kubeval --schema-location file:///doc/kubernetes-json-schema  test.yaml
The document test.yaml contains a valid Service
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;问题&#34;&gt;问题&lt;/h2&gt;

&lt;p&gt;似乎目前对 1.8 之前的版本支持还不够好，慎用。&lt;/p&gt;

&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;项目地址：&lt;code&gt;https://github.com/instrumenta/kubeval&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;文档结构库：&lt;code&gt;https://github.com/instrumenta/kubernetes-json-schema&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用说明：&lt;code&gt;https://kubeval.instrumenta.dev/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：Ksniff</title>
      <link>/post/intro-ksniff/</link>
      <pubDate>Wed, 05 Jun 2019 00:22:15 +0800</pubDate>
      <guid>/post/intro-ksniff/</guid>
      <description>

&lt;p&gt;对于相当一部分读者来说，在 Kubernetes 环境中，针对 Pod 进行抓包是个常规操作，在 Pod 中、在 Node 中都能够完成，抓出文件之后现场查看或者拷贝回来喂给 Wireshark 也都不难。&lt;a href=&#34;https://github.com/eldadru/ksniff&#34; target=&#34;_blank&#34;&gt;Ksniff&lt;/a&gt;工具的作用是，把这些常规步骤组织起来，用一个简单的 kubectl 插件命令，就能完成这一系列的操作。&lt;/p&gt;

&lt;p&gt;Ksniff 有几个很有意思的特色：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;可以使用 &lt;a href=&#34;https://github.com/kubernetes-sigs/krew&#34; target=&#34;_blank&#34;&gt;krew&lt;/a&gt; 方便的进行安装。&lt;/li&gt;
&lt;li&gt;能够自动把 Pod 的 TCP Dump 数据输出给 Wireshark。&lt;/li&gt;
&lt;li&gt;能够方便的处理非特权 Pod 的抓包工作。&lt;/li&gt;
&lt;li&gt;无需触碰 Node。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;使用 Krew 能够很方便的安装 Ksniff：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl krew install sniff
Updated the local copy of plugin index.
Installing plugin: sniff
CAVEATS:
\
 |  This plugin needs the following programs:
 |  * wireshark (optional, used for live capture)
/
Installed plugin: sniff
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;抓包到-wireshark&#34;&gt;抓包到 Wireshark&lt;/h2&gt;

&lt;p&gt;部署一个简单的 httpbin 服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: httpbin
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;服务启动之后，再启动一个客户端：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sleep
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sleep
        version: v1
    spec:
      containers:
        - name: sleep
          image: dustise/sleep
          imagePullPolicy: IfNotPresent
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后就可以启动 &lt;code&gt;ksniff&lt;/code&gt; 插件来对 &lt;code&gt;httpbin&lt;/code&gt; 的 Pod 进行监听了，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl sniff httpbin-5fc7cf895d-lr89b
...
INFO[0000] sniffing method: upload static tcpdump
...
INFO[0000] using tcpdump path at: &#39;/Users/dustise/.krew/store/sniff/

。。。INFO[0002] executing command: &#39;[/tmp/static-tcpdump -i any -U -w - ]&#39; on container: &#39;httpbin&#39;, pod: &#39;httpbin-5fc7cf895d-lr89b&#39;, namespace: &#39;default&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不难看出，ksniff 非常粗暴的将一个 tcpdump 上传到了被抓包的 Pod 上直接运行。并且命令执行后，直接启动了 Wireshark 进行监听。&lt;/p&gt;

&lt;p&gt;下面从 sleep Pod 上给被监听 Pod 制造一点流量。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl exec -it sleep-69bd44b5bb-tk6vn -- curl http://httpbin:8000/ip
{
  &amp;quot;origin&amp;quot;: &amp;quot;10.244.0.19&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在 Wireshark 中会看到相应的数据包：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/wireshark.png&#34; alt=&#34;wireshark&#34; /&gt;&lt;/p&gt;

&lt;p&gt;查看一下被监听 Pod 的进程：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl exec -it httpbin-5fc7cf895d-lr89b -- ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.6  85980 25100 ?        Ss   15:42   0:01 /usr/bin/python
root         8  0.0  0.8 130364 35164 ?        S    15:42   0:01 /usr/bin/python
root        35  0.0  0.0   6392  3568 ?        Ss   15:50   0:00 /tmp/static-tcp
root        47  0.0  0.0   6392  3564 ?        Ss   15:58   0:00 /tmp/static-tcp
root        70  0.0  0.0   6392  3564 ?        Ss   16:17   0:00 /tmp/static-tcp
root        90  0.0  0.0   6392  3568 ?        Ss   17:01   0:00 /tmp/static-tcp
root       102  0.0  0.0   6392  3568 ?        Ss   17:05   0:00 /tmp/static-tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不难看到，多出了几个 &lt;code&gt;/tmp/static-tcp&lt;/code&gt; 的进程。&lt;/p&gt;

&lt;h2 id=&#34;无特权-pod-怎么办&#34;&gt;无特权 Pod 怎么办&lt;/h2&gt;

&lt;p&gt;Ksniff 还提供了 &lt;code&gt;-p&lt;/code&gt; 参数，用于针对无特权 Pod 进行监听。带有这一参数之后，查询目标 Pod 所在节点，然后在该节点上利用节点亲和性创建共享节点网络的特权 Pod，然后在新 Pod 上对流量进行监控。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl sniff httpbin-5fc7cf895d-lr89b -p                                                                 1.1  ✱
INFO[0000] sniffing method: privileged pod
INFO[0000] using tcpdump path at: &#39;/Users/dustise/.krew/store/sniff/71102253eded8900c8f7b0d0624c65b3c77ecd6bcd28fabc9a200da
ac502282a/static-tcpdump&#39;
INFO[0000] no container specified, taking first container we found in pod.
INFO[0000] selected container: &#39;httpbin&#39;
...
INFO[0000] creating privileged pod on node: &#39;vla&#39;
...
INFO[0008] pod: &#39;ksniff-qpznn&#39; created successfully on node: &#39;vla&#39;

$ kubectl get pods

flaskapp-v1-5f58cbc685-9v4z9   1/1     Running   0          92m
httpbin-5fc7cf895d-lr89b       1/1     Running   0          93m
ksniff-689sx                   1/1     Running   0          66m
sleep-69bd44b5bb-tk6vn         1/1     Running   0          93m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，ksniff 创建了新的 Pod。并且也成功的启动了 Wireshark。再次执行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl exec -it sleep-69bd44b5bb-tk6vn -- curl http://httpbin:8000/ip
{
  &amp;quot;origin&amp;quot;: &amp;quot;10.244.0.19&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，Wireshark 中出现了新的数据包。&lt;/p&gt;

&lt;h2 id=&#34;参考链接&#34;&gt;参考链接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://github.com/kubernetes-sigs/krew&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/eldadru/ksniff&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：Kubedog</title>
      <link>/post/intro-kubedog/</link>
      <pubDate>Sun, 02 Jun 2019 14:59:25 +0800</pubDate>
      <guid>/post/intro-kubedog/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://github.com/flant/kubedog&#34; target=&#34;_blank&#34;&gt;Kubedog&lt;/a&gt; 是一个开源的 Golang 项目，使用 watch 方式对 Kubernetes 资源进行跟踪，能够方便的用于日常运维和 CI/CD 过程之中，项目中除了一个 CLI 小工具之外，还提供了一组 SDK，用户可以将其中的 Watch 功能集成到自己的系统之中。安装过程非常简单，在项目网页直接下载即可。&lt;/p&gt;

&lt;p&gt;Kubedog CLI 有两个功能：&lt;code&gt;rollout track&lt;/code&gt; 和 &lt;code&gt;follow&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;rollout-track&#34;&gt;rollout track&lt;/h2&gt;

&lt;p&gt;在 Kubernetes 上运行应用时，通常的做法是使用 &lt;code&gt;kubectl apply&lt;/code&gt; 提交 YAML 之后，使用 &lt;code&gt;kubectl get -w&lt;/code&gt; 或者 &lt;code&gt;watch kubectl get&lt;/code&gt; 之类的命令等待 Pod 启动。如果启动成功，则进行测试等后续动作；如果启动失败，就需要用 &lt;code&gt;kubectl logs&lt;/code&gt;、&lt;code&gt;kubectl describe&lt;/code&gt; 等命令来查看失败原因。kubedog 能在一定程度上简化这一过程。&lt;/p&gt;

&lt;p&gt;例如使用 &lt;code&gt;kubectl run&lt;/code&gt; 命令创建一个新的 Deployment 资源，并使用 &lt;code&gt;kubedog&lt;/code&gt; 跟进创建进程：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl run nginx --image=nginx22
...
deployment.apps/nginx created

$ kubedog rollout track deployment nginx
# deploy/nginx added
# deploy/nginx rs/nginx-6cc78cbf64 added
# deploy/nginx po/nginx-6cc78cbf64-8pnjz added
# deploy/nginx po/nginx-6cc78cbf64-8pnjz nginx error: ImagePullBackOff: Back-off pulling image &amp;quot;nginx22&amp;quot;
deploy/nginx po/nginx-6cc78cbf64-8pnjz nginx failed: ImagePullBackOff: Back-off pulling image &amp;quot;nginx22&amp;quot;

$ echo $?
130
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;很方便的看出，运行失败的状态及其原因，并且可以使用返回码来进行判断，方便在 Pipeline 中的运行。接下来可以使用 &lt;code&gt;kubectl edit&lt;/code&gt; 命令编辑 Deployment，修改正确的镜像名称。然后再次进行验证：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl edit deployment nginx
deployment.extensions/nginx edited
$ kubedog rollout track deployment nginx
# deploy/nginx added
# deploy/nginx rs/nginx-dbddb74b8 added
# deploy/nginx po/nginx-dbddb74b8-x4nkm added
# deploy/nginx event: po/nginx-dbddb74b8-x4nkm Pulled: Successfully pulled image &amp;quot;nginx&amp;quot;
# deploy/nginx event: po/nginx-dbddb74b8-x4nkm Created: Created container
# deploy/nginx event: po/nginx-dbddb74b8-x4nkm Started: Started container
# deploy/nginx event: ScalingReplicaSet: Scaled down replica set nginx-6cc78cbf64 to 0
# deploy/nginx become READY
$ echo $?
0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;修改完成，重新运行 kubedog，会看到成功运行的情况，并且返回值也变成了 0。&lt;/p&gt;

&lt;h2 id=&#34;follow&#34;&gt;follow&lt;/h2&gt;

&lt;p&gt;follow 命令的功能和 &lt;a href=&#34;https://github.com/johanhaleby/kubetail&#34; target=&#34;_blank&#34;&gt;kubetail&lt;/a&gt; 的功能有少量重叠，可以用 Deployment/Job/Daemonset 等为单位，查看其中所有 Pod 的日志，例如前面用的 Nginx，如果有访问的话，就会看到如下结果：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubedog follow deployment nginx
# deploy/nginx appears to be ready
# deploy/nginx rs/nginx-6cc78cbf64 added
# deploy/nginx new rs/nginx-dbddb74b8 added
# deploy/nginx rs/nginx-dbddb74b8(new) po/nginx-dbddb74b8-x4nkm added
# deploy/nginx rs/nginx-6cc54845d9 added
# deploy/nginx event: ScalingReplicaSet: Scaled up replica set nginx-6cc54845d9 to 1
# deploy/nginx rs/nginx-6cc54845d9(new) po/nginx-6cc54845d9-nhlvs added
# deploy/nginx event: po/nginx-6cc54845d9-nhlvs Pulling: pulling image &amp;quot;nginx:alpine&amp;quot;
# deploy/nginx event: po/nginx-6cc54845d9-nhlvs Pulled: Successfully pulled image &amp;quot;nginx:alpine&amp;quot;
# deploy/nginx event: po/nginx-6cc54845d9-nhlvs Created: Created container
# deploy/nginx event: po/nginx-6cc54845d9-nhlvs Started: Started container
# deploy/nginx event: ScalingReplicaSet: Scaled down replica set nginx-dbddb74b8 to 0
# deploy/nginx become READY
# deploy/nginx event: po/nginx-dbddb74b8-x4nkm Killing: Killing container with id docker://nginx:Need to kill Pod
&amp;gt;&amp;gt; deploy/nginx rs/nginx-dbddb74b8 po/nginx-dbddb74b8-x4nkm nginx

&amp;gt;&amp;gt; deploy/nginx rs/nginx-6cc54845d9(new) po/nginx-6cc54845d9-nhlvs nginx
127.0.0.1 - - [02/Jun/2019:11:35:08 +0000] &amp;quot;GET / HTTP/1.1&amp;quot; 200 612 &amp;quot;-&amp;quot; &amp;quot;Wget&amp;quot; &amp;quot;-&amp;quot;
127.0.0.1 - - [02/Jun/2019:11:35:11 +0000] &amp;quot;GET / HTTP/1.1&amp;quot; 200 612 &amp;quot;-&amp;quot; &amp;quot;Wget&amp;quot; &amp;quot;-&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;项目地址&#34;&gt;项目地址&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;https://github.com/flant/kubedog&lt;/code&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Rook &amp; Ceph 简介</title>
      <link>/post/the-ultimate-rook-and-ceph-survival-guide/</link>
      <pubDate>Fri, 17 May 2019 09:38:33 +0800</pubDate>
      <guid>/post/the-ultimate-rook-and-ceph-survival-guide/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://www.cloudops.com/2019/05/the-ultimate-rook-and-ceph-survival-guide/&#34; target=&#34;_blank&#34;&gt;The Ultimate Rook and Ceph Survival Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在容器世界中，无状态是一个核心原则，然而我们始终需要保存数据，并提供给他人进行访问。所以就需要一个方案用于保持数据，以备重启之需。&lt;/p&gt;

&lt;p&gt;在 Kubernetes 中，PVC 是管理有状态应用的一个推荐方案。有了 PVC 的帮助，Pod 可以申请并连接到存储卷，这些存储卷在 Pod 生命周期结束之后，还能独立存在。&lt;/p&gt;

&lt;p&gt;PVC 在存储方面让开发和运维的职责得以分离。运维人员负责供应存储，而开发人员则可以在不知后端细节的情况下，申请使用这些存储卷。&lt;/p&gt;

&lt;p&gt;PVC 由一系列组件构成：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/volumes/#persistentvolumeclaim&#34; target=&#34;_blank&#34;&gt;PVC&lt;/a&gt;：是 Pod 对存储的请求。PVC 会被 Pod 动态加载成为一个存储卷。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/persistent-volumes/&#34; target=&#34;_blank&#34;&gt;PV&lt;/a&gt;，可以由运维手工分配，也可以使用 &lt;code&gt;StorageClass&lt;/code&gt; 动态分配。PV 受 Kubernetes 管理，但并不与特定的 Pod 直接绑定。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/storage-classes/&#34; target=&#34;_blank&#34;&gt;StorageClass&lt;/a&gt;：由管理员创建，可以用来动态的创建存储卷和 PV。&lt;/p&gt;

&lt;p&gt;物理存储：实际连接和加载的存储卷。&lt;/p&gt;

&lt;p&gt;分布式存储系统是一个有效的解决有状态工作负载高可用问题的方案。Ceph 就是一个分布式存储系统，近年来其影响主键扩大。Rook 是一个编排器，能够支持包括 Ceph 在内的多种存储方案。Rook 简化了 Ceph 在 Kubernetes 集群中的部署过程。&lt;/p&gt;

&lt;p&gt;在生产环境中使用 Rook + Ceph 组合的用户正在日益增加，尤其是自建数据中心的用户，&lt;a href=&#34;https://www.cengn.ca/&#34; target=&#34;_blank&#34;&gt;CENGN&lt;/a&gt;、Gini、GPR 等很多组织都在进行评估。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/rookceph1.png&#34; alt=&#34;images&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;ceph-是什么&#34;&gt;Ceph 是什么&lt;/h2&gt;

&lt;p&gt;Ceph 是一个分布式存储系统，具备大规模、高性能、无单点失败的特点。Ceph 是一个软件定义的系统，也就是说他可以运行在任何符合其要求的硬件之上。&lt;/p&gt;

&lt;p&gt;Ceph 包括多个组件：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ceph Monitors(MON)&lt;/strong&gt;：负责生成集群票选机制。所有的集群节点都会向 Mon 进行汇报，并在每次状态变更时进行共享信息。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ceph Object Store Devices(OSD)&lt;/strong&gt;：负责在本地文件系统保存对象，并通过网络提供访问。通常 OSD 守护进程会绑定在集群的一个物理盘上，Ceph 客户端直接和 OSD 打交道。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ceph Manager(MGR)&lt;/strong&gt;：提供额外的监控和界面给外部的监管系统使用。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliable Autonomic Distributed Object Stores&lt;/strong&gt;：Ceph 存储集群的核心。这一层用于为存储数据提供一致性保障，执行数据复制、故障检测以及恢复等任务。&lt;/p&gt;

&lt;p&gt;为了在 Ceph 上进行读写，客户端首先要联系 MON，获取最新的集群地图，其中包含了集群拓扑以及数据存储位置的信息。Ceph 客户端使用集群地图来获知需要交互的 OSD，从而和特定 OSD 建立联系。&lt;/p&gt;

&lt;h2 id=&#34;rook-是什么&#34;&gt;Rook 是什么&lt;/h2&gt;

&lt;p&gt;Rook 是一个可以提供 Ceph 集群管理能力的 &lt;a href=&#34;https://coreos.com/blog/introducing-operators.html&#34; target=&#34;_blank&#34;&gt;Operator&lt;/a&gt;。Rook 使用 CRD 一个控制器来对 Ceph 之类的资源进行部署和管理。&lt;/p&gt;

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

&lt;p&gt;Rook 包含多个组件：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rook Operator&lt;/strong&gt;：Rook 的核心组件，Rook Operator 是一个简单的容器，自动启动存储集群，并监控存储守护进程，来确保存储集群的健康。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rook Agent&lt;/strong&gt;：在每个存储节点上运行，并配置一个 FlexVolume 插件，和 Kubernetes 的存储卷控制框架进行集成。Agent 处理所有的存储操作，例如挂接网络存储设备、在主机上加载存储卷以及格式化文件系统等。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rook Discovers&lt;/strong&gt;：检测挂接到存储节点上的存储设备。&lt;/p&gt;

&lt;p&gt;Rook 还会用 Kubernetes Pod 的形式，部署 Ceph 的 MON、OSD 以及 MGR 守护进程。&lt;/p&gt;

&lt;p&gt;Rook Operator 让用户可以通过 CRD 的是用来创建和管理存储集群。每种资源都定义了自己的 CRD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rook &lt;a href=&#34;https://rook.github.io/docs/rook/master/cluster-crd.html&#34; target=&#34;_blank&#34;&gt;Cluster&lt;/a&gt;&lt;/strong&gt;：提供了对存储机群的配置能力，用来提供块存储、对象存储以及共享文件系统。每个集群都有多个 Pool。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://rook.github.io/docs/rook/master/pool-crd.html&#34; target=&#34;_blank&#34;&gt;Pool&lt;/a&gt;&lt;/strong&gt;：为块存储提供支持。Pool 也是给文件和对象存储提供内部支持。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://rook.github.io/docs/rook/master/object-store-crd.html&#34; target=&#34;_blank&#34;&gt;Object Store&lt;/a&gt;&lt;/strong&gt;：用 S3 兼容接口开放存储服务。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://rook.github.io/docs/rook/master/filesystem-crd.html&#34; target=&#34;_blank&#34;&gt;File System&lt;/a&gt;&lt;/strong&gt;：为多个 Kubernetes Pod 提供共享存储。&lt;/p&gt;

&lt;h2 id=&#34;在-kubernetes-上部署-rook&#34;&gt;在 Kubernetes 上部署 Rook&lt;/h2&gt;

&lt;p&gt;下面我们会在 Kubernetes 上分步骤部署 Rook，并在同一集群中作为客户端来使用其存储服务。Ceph 需要额外的驱动来保存数据，因此建议提供一组独立的存储节点。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/rook-k8s.png&#34; alt=&#34;k8s and rook&#34; /&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Helm&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Kubernetes（启用 RBAC）&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;磁盘配置&#34;&gt;磁盘配置&lt;/h3&gt;

&lt;p&gt;这里假设在存储节点上配有未格式化的磁盘。为了提供最佳性能，你可能需要在单独的设备上启用 WAL（本文就不深入讨论了）。&lt;/p&gt;

&lt;h3 id=&#34;配置-flexvolume-如果需要&#34;&gt;配置 FlexVolume（如果需要）&lt;/h3&gt;

&lt;p&gt;Rook agent 使用 &lt;a href=&#34;https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md&#34; target=&#34;_blank&#34;&gt;FlexVolume&lt;/a&gt; 来和 Kubernetes 进行集成，从而进行存储操作。为了达成这一目标，Rook agent 要在每个节点部署 Rook FlexVolume。&lt;/p&gt;

&lt;p&gt;在一些情况下，FlexVolume 的缺省目录是只读的，例如 Rancher 和 CoreOS。在这种情况下就需要配置 Kubelet 使用不同的可写入的目录了。&lt;/p&gt;

&lt;p&gt;如果使用的是 Rancher Kubernetes Engine（RKE），可以用下面的方式配置 kubelet，然后使用 &lt;code&gt;rke up&lt;/code&gt; 应用配置。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kubelet:
  image: &amp;quot;&amp;quot;
  extra_args:
    volume-plugin-dir: /usr/libexec/kubernetes/kubelet-plugins/volume/exec
  extra_binds:
    - /usr/libexec/kubernetes/kubelet-plugins/volume/exec:/usr/libexec/kubernetes/kubelet-plugins/volume/exec
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;安装-rook-operator&#34;&gt;安装 Rook Operator&lt;/h3&gt;

&lt;p&gt;在 Helm 中加入 &lt;code&gt;Rook Charts&lt;/code&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;helm repo add rook-stable https://charts.rook.io/stable
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;安装 Rook Operator（当前版本为 v0.9.3）&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;helm install --name rook --namespace rook-ceph-system rook-stable/rook-ceph

$ kubectl get po -n rook-ceph-system -o wide
NAME                                  READY   STATUS    RESTARTS   AGE   IP              NODE                      NOMINATED NODE
rook-ceph-agent-gwl8s                 1/1     Running   0          35d   10.212.144.51   coo-r1-k8s-worker-st-01
rook-ceph-agent-lqkjl                 1/1     Running   0          35d   10.212.144.52   coo-r1-k8s-worker-st-02
rook-ceph-agent-x66sw                 1/1     Running   0          35d   10.212.144.53   coo-r1-k8s-worker-st-03
rook-ceph-operator-7d44ddfdcb-q5chh   1/1     Running   0          35d   10.244.8.3      coo-r1-k8s-worker-st-03
rook-discover-fmqrd                   1/1     Running   0          35d   10.244.7.5      coo-r1-k8s-worker-st-01
rook-discover-jlsv9                   1/1     Running   0          35d   10.244.6.3      coo-r1-k8s-worker-st-02
rook-discover-vt7mk                   1/1     Running   0          35d   10.244.8.4      coo-r1-k8s-worker-st-03
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Helm 会部署 Rook 的相关 Pod（rook-operator、rook-discover 以及 rook-agent），以及相关的 CRD。Discover Pod 会运行一个发现脚本，来查找挂接到 Kubernetes 存储节点上的本地存储设备。&lt;/p&gt;

&lt;p&gt;请注意，&lt;code&gt;rook-ceph-system&lt;/code&gt; 中的所有 Pod 都应该是 &lt;code&gt;Running&lt;/code&gt; 或者 &lt;code&gt;Completed&lt;/code&gt; 状态，不应存在 &lt;code&gt;restarts&lt;/code&gt; 或 &lt;code&gt;error&lt;/code&gt; 的情况。&lt;/p&gt;

&lt;h3 id=&#34;创建受-root-管理的-ceph-集群&#34;&gt;创建受 Root 管理的 Ceph 集群&lt;/h3&gt;

&lt;p&gt;下一步就是创建 Ceph 集群。在 &lt;a href=&#34;https://github.com/rook/rook&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;rook&lt;/code&gt; 源码&lt;/a&gt;中找到 &lt;code&gt;cluster/examples/kubernetes/ceph/cluster.yaml&lt;/code&gt;，进行查看和修改。集群 CRD 中定义了存储集群的内容。下面的命令就能够启动一个 Rook 集群：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl create -f cluster.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;要确认我们的 Rook 集群的工作状况，可以检查一下 &lt;code&gt;rook-ceph&lt;/code&gt; 命名空间中的 Pod 运行情况：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl get po -n rook-ceph -o wide
NAME                                                  READY   STATUS      RESTARTS   AGE   IP              NODE                      NOMINATED NODE
rook-ceph-mgr-a-bf78cdcb8-q4gpz                       1/1     Running     0          35d   10.244.7.7      coo-r1-k8s-worker-st-01
rook-ceph-mon-a-755d985488-72kzh                      1/1     Running     0          35d   10.244.7.6      coo-r1-k8s-worker-st-01
rook-ceph-mon-b-845c97f94b-h5jlp                      1/1     Running     0          35d   10.244.6.4      coo-r1-k8s-worker-st-02
rook-ceph-mon-c-68b495d97d-m524q                      1/1     Running     0          35d   10.244.8.7      coo-r1-k8s-worker-st-03
rook-ceph-osd-0-56b7b86b5b-kz882                      1/1     Running     0          35d   10.244.7.9      coo-r1-k8s-worker-st-01
rook-ceph-osd-1-6d9558b6bd-xlkzf                      1/1     Running     0          35d   10.244.6.6      coo-r1-k8s-worker-st-02
rook-ceph-osd-2-56bf4b6c64-2p9rp                      1/1     Running     0          35d   10.244.8.9      coo-r1-k8s-worker-st-03
rook-ceph-osd-3-86ccf5d69f-xzjmz                      1/1     Running     0          35d   10.244.7.10     coo-r1-k8s-worker-st-01
rook-ceph-osd-4-6f469fc877-bt799                      1/1     Running     0          35d   10.244.6.7      coo-r1-k8s-worker-st-02
rook-ceph-osd-5-6549cdf949-qbvnh                      1/1     Running     0          35d   10.244.7.11     coo-r1-k8s-worker-st-01
rook-ceph-osd-6-7f56d8cf95-qgd8p                      1/1     Running     0          35d   10.244.6.8      coo-r1-k8s-worker-st-02
rook-ceph-osd-7-55b6c5c8df-dnp4p                      1/1     Running     0          35d   10.244.8.11     coo-r1-k8s-worker-st-03
rook-ceph-osd-8-d6df7694-w2psw                        1/1     Running     0          35d   10.244.8.10     coo-r1-k8s-worker-st-03
rook-ceph-osd-prepare-coo-r1-k8s-worker-st-01-zbs6m   0/2     Completed   0          35d   10.244.7.8      coo-r1-k8s-worker-st-01
rook-ceph-osd-prepare-coo-r1-k8s-worker-st-02-sr2dm   0/2     Completed   0          35d   10.244.6.5      coo-r1-k8s-worker-st-02
rook-ceph-osd-prepare-coo-r1-k8s-worker-st-03-zzqmq   0/2     Completed   0          35d   10.244.8.8      coo-r1-k8s-worker-st-03
rook-ceph-tools-cb5655595-vq4vj                       1/1     Running     0          35d   10.212.144.53   coo-r1-k8s-worker-st-03
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到 &lt;code&gt;mon&lt;/code&gt;、&lt;code&gt;osd-prepare&lt;/code&gt; 已经部署。&lt;code&gt;rook-ceph-osd-prepare&lt;/code&gt; 格式化了磁盘，准备了 OSD，并把 &lt;code&gt;osd&lt;/code&gt; Pod 加入了集群。&lt;/p&gt;

&lt;p&gt;Rook 还提供了一个 &lt;code&gt;toolkit&lt;/code&gt; 容器，其中包含了全套的 Ceph 客户端，用于测试和排错，运行下列命令即可安装：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl create -f toolkit.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;进入工具 Pod，就可以执行下面的内容了。&lt;/p&gt;

&lt;h3 id=&#34;集群配置&#34;&gt;集群配置&lt;/h3&gt;

&lt;p&gt;设置 Ceph 组件的资源：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;mgr:
    limits:
        cpu: &amp;quot;500m&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
    requests:
        cpu: &amp;quot;500m&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
mon:
    limits:
        cpu: &amp;quot;1&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
    requests:
        cpu: &amp;quot;500m&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
osd:
    limits:
        cpu: &amp;quot;1&amp;quot;
        memory: &amp;quot;2048Mi&amp;quot;
    requests:
        cpu: &amp;quot;500m&amp;quot;
        memory: &amp;quot;1024Mi&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;建议尽量为所有存储节点分配同样的 CPU、内存和磁盘。这样就可以使用 &lt;code&gt;deviceFilter&lt;/code&gt; 了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;storage:
  useAllNodes: true
  useAllDevices: false
  deviceFilter: sd[a-z]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里使用正则表达式 &lt;code&gt;/dev/sd[a-z]&lt;/code&gt; 进行设备匹配。&lt;/p&gt;

&lt;h3 id=&#34;创建-ceph-副本池以及-kubernetes-storageclass&#34;&gt;创建 Ceph 副本池以及 Kubernetes StorageClass&lt;/h3&gt;

&lt;p&gt;可以用 CRD 来定义 Pool。Rook 提供了两种机制来维持 OSD：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;副本&lt;/strong&gt;：缺省选项，每个对象都会根据 &lt;code&gt;spec.replicated.size&lt;/code&gt; 在多个磁盘上进行复制。建议非生产环境至少 2 个副本，生产环境至少 3 个。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Erasure Code&lt;/strong&gt;：是一种较为节约的方式。EC 把数据拆分 n 段（&lt;code&gt;spec.erasureCoded.dataChunks&lt;/code&gt;），再加入 k 个代码段（&lt;code&gt;spec.erasureCoded.codingChunks&lt;/code&gt;），用分布的方式把 n+k 段数据保存在磁盘上。这种情况下 Ceph 能够隔离 &lt;code&gt;k&lt;/code&gt; 个 OSD 的损失。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# pool with replication enabled
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicated-metadata-pool
  namespace: rook-ceph
spec:
  replicated:
    size: 2
---
# pool with EC enabled
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: ec-data-pool
  namespace: rook-ceph
spec:
  erasureCoded:
    dataChunks: 2
    codingChunks: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;本文中使用副本的方式来保证数据冗余。&lt;/p&gt;

&lt;p&gt;Kubernetes 环境里，StorageClass 是动态存储配置的核心。下面的例子定义了一个 Ceph 块存储的 StorageClass：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: rook-ceph-block
provisioner: ceph.rook.io/block
parameters:
  # The replicated pool as the `blockPool` parameter
  blockPool: replicated-metadata-pool
  # The erasure coded pool must be set as the `dataBlockPool` parameter below.
  dataBlockPool: ec-data-pool
  clusterNamespace: rook-ceph
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl&lt;/code&gt; 提交 &lt;code&gt;storageclass.yaml&lt;/code&gt; 以及 &lt;code&gt;cluster.yaml&lt;/code&gt;，就完成了 Ceph 副本和 StorageClass 的创建。&lt;/p&gt;

&lt;h2 id=&#34;测试&#34;&gt;测试&lt;/h2&gt;

&lt;p&gt;使用上面创建的 &lt;code&gt;StorageClass&lt;/code&gt;，新建一个 PVC，就可以完成测试了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  storageClassName: rook-ceph-block
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查新建的 PVC，看状态是不是会变成 &lt;code&gt;Bounded&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl get pvc
NAMESPACE       NAME                                     STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
default   mysql-pv-claim                                 Bound     pvc-f1af6df6-474a-11e9-8360-02006e76001e   8Gi        RWO            rook-ceph-block   1m
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;ceph-常用命令&#34;&gt;Ceph 常用命令&lt;/h2&gt;

&lt;p&gt;在工具箱 Pod 中，可以使用命令和 Ceph 集群进行交互。下面是一个例子。&lt;/p&gt;

&lt;h3 id=&#34;查看集群状态&#34;&gt;查看集群状态&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ ceph status
  cluster:
    id:     62e69dc1-efb5-42d9-a7bc-1ea6cfbd467f
    health: HEALTH_OK

  services:
    mon: 3 daemons, quorum c,a,b
    mgr: a(active)
    osd: 9 osds: 9 up, 9 in

  data:
    pools:   1 pools, 100 pgs
    objects: 236  objects, 406 MiB
    usage:   10 GiB used, 1.7 TiB / 1.8 TiB avail
    pgs:     100 active+clean

  io:
    client:   38 KiB/s wr, 0 op/s rd, 3 op/s wr
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;健康情况&#34;&gt;健康情况&lt;/h3&gt;

&lt;p&gt;用来查看是否有物理损坏。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ ceph health detail
HEALTH_OK
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;所有-osd-的状态&#34;&gt;所有 OSD 的状态&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ ceph osd status
+----+-------------------------+-------+-------+--------+---------+--------+---------+-----------+
| id |           host          |  used | avail | wr ops | wr data | rd ops | rd data |   state   |
+----+-------------------------+-------+-------+--------+---------+--------+---------+-----------+
| 0  | coo-r1-k8s-worker-st-01 | 1149M |  198G |    0   |  13.6k  |    0   |     0   | exists,up |
| 1  | coo-r1-k8s-worker-st-02 | 1157M |  198G |    0   |     0   |    0   |     0   | exists,up |
| 2  | coo-r1-k8s-worker-st-03 | 1143M |  198G |    0   |     0   |    0   |     0   | exists,up |
| 3  | coo-r1-k8s-worker-st-01 | 1128M |  198G |    0   |     0   |    0   |     0   | exists,up |
| 4  | coo-r1-k8s-worker-st-02 | 1180M |  198G |    4   |  37.6k  |    0   |     0   | exists,up |
| 5  | coo-r1-k8s-worker-st-01 | 1169M |  198G |    1   |  12.0k  |    0   |     0   | exists,up |
| 6  | coo-r1-k8s-worker-st-02 | 1109M |  198G |    0   |     0   |    0   |     0   | exists,up |
| 7  | coo-r1-k8s-worker-st-03 | 1160M |  198G |    0   |  1638   |    0   |     0   | exists,up |
| 8  | coo-r1-k8s-worker-st-03 | 1143M |  198G |    0   |  3276   |    0   |     0   | exists,up |
+----+-------------------------+-------+-------+--------+---------+--------+---------+-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;ceph-pool-详情&#34;&gt;Ceph Pool 详情&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ ceph osd pool ls detail
pool 1 &#39;replicapool&#39; replicated size 3 min_size 1 crush_rule 1 object_hash rjenkins pg_num 100 pgp_num 100 last_change 37 flags hashpspool,selfmanaged_snaps stripe_width 0 application rbd
removed_snaps [1~3]
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;显示-pool-和总体用量&#34;&gt;显示 Pool 和总体用量&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ rados df
POOL_NAME      USED OBJECTS CLONES COPIES MISSING_ON_PRIMARY UNFOUND DEGRADED RD_OPS      RD  WR_OPS     WR
replicapool 1.4 GiB     575      0   1150                  0       0        0   1224 2.4 MiB 1698291 84 GiB

total_objects    575
total_used       5.6 GiB
total_avail      294 GiB
total_space      300 GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;重新安装&#34;&gt;重新安装&lt;/h2&gt;

&lt;h3 id=&#34;包括-rke-的完全重新部署&#34;&gt;包括 RKE 的完全重新部署&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;下面的步骤会擦除数据，不建议在生产集群上使用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rook 有很多数据保存在本地存储节点，重新部署比较困难。如果使用的是 RKE，在 Worker 和 Master 节点上清除数据，然后重新安装 RKE。&lt;/p&gt;

&lt;p&gt;删除所有 Docker、Rook 和 RKE 及其相关组件：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# remove rke docker and everything
sudo apt -y purge docker-ce \
&amp;amp;&amp;amp; sudo apt -y autoremove \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/docker \
&amp;amp;&amp;amp; sudo rm -rf /opt/* \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/rook \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/etcd \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/cni \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/containerd \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/calico \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/kubelet \
&amp;amp;&amp;amp; sudo rm -rf /var/lib/rancher
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果是一个运行中的服务器，完成这一命令之后建议重新启动，然后重复执行一次，最后再重新安装（包括 Docker）。&lt;/p&gt;

&lt;p&gt;还需要格式化 Rook/Ceph 使用的磁盘。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo fdisk /dev/sdb
Welcome to fdisk (util-linux 2.29.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device /dev/sdb already contains a LVM2_member signature.
The signature will be removed by a write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0xa24124a7.

Command (m for help):
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;简单的输入 &lt;code&gt;w&lt;/code&gt;，然后回车，就会覆盖分区表，让磁盘恢复可用。这一动作完成之后也建议重新启动。&lt;/p&gt;

&lt;h3 id=&#34;只重装-rook&#34;&gt;只重装 Rook&lt;/h3&gt;

&lt;p&gt;删除 &lt;code&gt;rook-ceph&lt;/code&gt; 以及 &lt;code&gt;rook-ceph-system&lt;/code&gt; 命名空间会造成很大麻烦。要关停 Ceph 集群：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;删除 Rook 集群的相关资源（Pool、StorageClass、PVC 等等）&lt;/p&gt;

&lt;p&gt;kubectl delete -n rook-ceph cephblockpool replicapool
kubectl delete storageclass rook-ceph-block&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;删除 cluster CRD 和 Helm release&lt;/p&gt;

&lt;p&gt;kubectl -n rook-ceph patch clusters.ceph.rook.io rook-ceph -p ‘{“metadata”:{“finalizers”: []}}’ –type=merge
kubectl -n rook-ceph delete cephcluster rook-ceph
helm delete –purge rook
kubectl delete namespace rook-ceph&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;连接到每个节点上删除 &lt;code&gt;/var/lib/rook&lt;/code&gt; 或者 &lt;code&gt;dataDirHostPath&lt;/code&gt; 指定的路径。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果出现任何问题，可以参考 &lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/ceph-teardown.md#troubleshooting&#34; target=&#34;_blank&#34;&gt;Trouble shooting 页面&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;检测&#34;&gt;检测&lt;/h2&gt;

&lt;h3 id=&#34;physical-group-修复&#34;&gt;Physical Group 修复&lt;/h3&gt;

&lt;p&gt;Ceph 偶尔会报告 Physical Group 需要修复，可以在工具箱 Pod 中完成：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ ceph health detail
HEALTH_ERR
1 pgs inconsistent; 2 scrub errors
pg 0.6 is active+clean+inconsistent, acting [0,1,2] 2 scrub errors
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的输出说明需要进行修复，执行下列命令：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ceph pg repair 0.6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个命令会启动一个修复过程，几分钟之后，会恢复到 &lt;code&gt;HEALTH_OK&lt;/code&gt; 状态。&lt;/p&gt;

&lt;h3 id=&#34;修改副本数量&#34;&gt;修改副本数量&lt;/h3&gt;

&lt;p&gt;如果副本数量设置有误，可以在运行中的副本池中修改设置，在工具箱 Pod 中执行：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ceph osd pool set replicapool size 3
ceph osd pool set replicapool min_size 3
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;修改-pg-数量&#34;&gt;修改 PG 数量&lt;/h3&gt;

&lt;p&gt;要修改副本池中的 PG 数，可以使用：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ceph osd pool set replicapool pg_num 256
ceph osd pool set replicapool pgp_num 256
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/ceph-toolbox.md&#34; target=&#34;_blank&#34;&gt;https://github.com/rook/rook/blob/master/Documentation/ceph-toolbox.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.ceph.com/docs/mimic/rados/troubleshooting/troubleshooting-pg/&#34; target=&#34;_blank&#34;&gt;http://docs.ceph.com/docs/mimic/rados/troubleshooting/troubleshooting-pg/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.ceph.com/docs/jewel/rados/operations/placement-groups/#set-the-number-of-placement-groups&#34; target=&#34;_blank&#34;&gt;http://docs.ceph.com/docs/jewel/rados/operations/placement-groups/#set-the-number-of-placement-groups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://accelazh.github.io/ceph/Ceph-Performance-Tuning-Checklist&#34; target=&#34;_blank&#34;&gt;http://accelazh.github.io/ceph/Ceph-Performance-Tuning-Checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/advanced-configuration.md&#34; target=&#34;_blank&#34;&gt;https://github.com/rook/rook/blob/master/Documentation/advanced-configuration.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/common-issues.md&#34; target=&#34;_blank&#34;&gt;https://github.com/rook/rook/blob/master/Documentation/common-issues.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.ceph.com/docs/giant/rados/troubleshooting/troubleshooting-osd/&#34; target=&#34;_blank&#34;&gt;http://docs.ceph.com/docs/giant/rados/troubleshooting/troubleshooting-osd/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>用 Sidecar 应用 Configmap 更新</title>
      <link>/post/refresh-cm-with-signal/</link>
      <pubDate>Thu, 16 May 2019 01:52:01 +0800</pubDate>
      <guid>/post/refresh-cm-with-signal/</guid>
      <description>

&lt;p&gt;在 Kubernetes 的使用过程中，很多人会使用 Configmap 资源来进行配置文件的加载。Configmap 对象是支持热更新的，也就是说，对 Configmap 的变更，会同时反应到加载该 Configmap 的 Pod 之中。但美中不足的是，很多应用都不会检测配置文件的更新，因此就算是通过对 Configmap 的变更，完成了配置文件的修改，应用还是无法做出即时的响应的。可以在外部进行滚动更新；或者改写业务容器，监控文件变化之后重新启动业务进程。&lt;/p&gt;

&lt;p&gt;在 Kubernetes 1.10 中新增的 Pod 内共享进程命名空间的功能，给这个问题带来了一点新思路：做一个 Sidecar 用于对配置文件进行监控，发现文件变化之后，发送重新载入的信号给业务进程，要求业务进程自行刷新。这样就无需对业务容器所在镜像进行修改了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这种方法当然也有个局限性，需要业务进程支持这种信号。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;下面以 Apache 为例，看看这种方式的用法。&lt;/p&gt;

&lt;h2 id=&#34;创建-configmap&#34;&gt;创建 Configmap&lt;/h2&gt;

&lt;p&gt;用一个简化的 &lt;code&gt;httpd.conf&lt;/code&gt; 文件，生成 Configmap，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl create cm apache --from-file httpd.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如此就生成了一个名为 apache 的 Configmap。&lt;/p&gt;

&lt;h2 id=&#34;创建-sidecar-容器镜像&#34;&gt;创建 Sidecar 容器镜像&lt;/h2&gt;

&lt;p&gt;这个镜像要完成的任务有两个：监控文件变化，如果内容变化，则发送信号给业务进程。文件内容变化的监控，可以用哈希码或者 inotify 调用来完成，这里使用 &lt;code&gt;inotifywait&lt;/code&gt; 命令做一个死循环，发现特定事件后，则发出信号：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
while :
do
  # 获取文件名称
  REAL=`readlink -f ${FILE}`
  # 监控指定事件
  inotifywait -e delete_self &amp;quot;${REAL}&amp;quot;
  # 获取特定进程名称的 PID
  PID=`pgrep ${PROCESS} | head -1`
  # 发送信号
  kill &amp;quot;-${SIGNAL}&amp;quot; &amp;quot;${PID}&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;这里没有用监控本地文件的 &lt;code&gt;-m&lt;/code&gt; 或者 &lt;code&gt;-e modify&lt;/code&gt; 事件，而是用了 &lt;code&gt;delete_self&lt;/code&gt;，这是 Configmap 加载生成文件的差异，也可以考虑用环境变量来替换这一事件。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;然后构建镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Dockerfile&#34;&gt;FROM alpine
RUN apk add --update inotify-tools
ENV FILE=&amp;quot;/tmp&amp;quot; PROCESS=&amp;quot;httpd&amp;quot; SIGNAL=&amp;quot;USR1&amp;quot;
COPY entry.sh /usr/local/bin
CMD [&amp;quot;/usr/local/bin/entry.sh&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里假设镜像名称为 &lt;code&gt;dustise/inotify:latest&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;创建实验负载&#34;&gt;创建实验负载&lt;/h2&gt;

&lt;p&gt;我们选择 Apache 作为业务应用的示范，它能够接受 &lt;code&gt;USR1&lt;/code&gt; 信号进行重新载入。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: apache
spec:
  selector:
    matchLabels:
      app: apache
  template:
    metadata:
      labels:
        app: apache
    spec:
      shareProcessNamespace: true
      containers:
      - name: apache
        image: httpd:alpine
        ports:
        - containerPort: 80
        volumeMounts:
          - name: apache
            mountPath: /usr/local/apache2/conf/
      - name: refresh
        image: dustise/inotify
        securityContext:
          capabilities:
            add:
            - SYS_PTRACE
        volumeMounts:
          - name: apache
            mountPath: /etc/httpd
        env:
          - name: FILE
            value: &amp;quot;/etc/httpd/httpd.conf&amp;quot;
          - name: PROCESS
            value: &amp;quot;httpd&amp;quot;
          - name: SIGNAL
            value: &amp;quot;USR1&amp;quot;
      volumes:
        - name: apache
          configMap:
            name: apache
---
apiVersion: v1
kind: Service
...
  type: ClusterIP
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段代码：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;template.spec&lt;/code&gt; 中加入了 &lt;code&gt;shareProcessNamespace: true&lt;/code&gt;，表示启用进行命名空间共享功能；&lt;/li&gt;
&lt;li&gt;新建了一个伴行的 Sidecar 容器；&lt;/li&gt;
&lt;li&gt;Apache 和 Sidecar 共享来自同一个 Configmap 的配置文件，根据加载情况为 Sidecar 定义了环境变量。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;测试一下&#34;&gt;测试一下&lt;/h2&gt;

&lt;p&gt;接下来可以使用 &lt;code&gt;kubectl logs&lt;/code&gt; 命令来监控两个容器的日志输出：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl logs -f apache-6b8b68c857-dp6xx -c refresh
Setting up watches.
Watches established.

$ kubectl logs -f apache-6b8b68c857-dp6xx -c apache
...
[Wed May 15 18:46:47.795261 2019] [mpm_event:notice] [pid 7:tid 139810635549544] AH00489: Apache/2.4.39 (Unix) configured -- resuming normal operations
[Wed May 15 18:46:47.795330 2019] [core:notice] [pid 7:tid 139810635549544] AH00094: Command line: &#39;httpd -D FOREGROUND&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后使用 &lt;code&gt;kubectl edit cm apache&lt;/code&gt;，修改配置文件（例如删除点注释）。稍候片刻，发现两个容器的输出都发生了变化：&lt;/p&gt;

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

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;/etc/httpd/..2019_05_15_18_43_33.773288813/httpd.conf DELETE_SELF
Setting up watches.
Watches established.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;脚本检测到了配置文件发生了删除事件，发送信号，并重新启动监控。&lt;/p&gt;

&lt;h3 id=&#34;apache&#34;&gt;Apache&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;[Wed May 15 18:46:47.775392 2019] [mpm_event:notice] [pid 7:tid 139810635549544] AH00493: SIGUSR1 received.  Doing graceful restart
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Apache 收到了 USR1 信号，进行了优雅重启。&lt;/p&gt;

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

&lt;p&gt;对于支持信号控制的第软件，例如 Nginx、Gunicorn、HA-Proxy 等都可以使用这种方式来完成配置刷新工作。能够有效的避免重启或修改业务应用的老大难问题。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 4 序章及目录</title>
      <link>/post/kubernetes-4-summary/</link>
      <pubDate>Wed, 15 May 2019 14:04:03 +0800</pubDate>
      <guid>/post/kubernetes-4-summary/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;/files/kubernetes-4-summary.pdf&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;images/k4-cover.jpeg&#34; alt=&#34;cover&#34; /&gt; PDF 下载&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 存储性能对比</title>
      <link>/post/kubernetes-storage-performance-comparison/</link>
      <pubDate>Mon, 13 May 2019 23:10:28 +0800</pubDate>
      <guid>/post/kubernetes-storage-performance-comparison/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://medium.com/vescloud/kubernetes-storage-performance-comparison-9e993cb27271&#34; target=&#34;_blank&#34;&gt;Kubernetes Storage Performance Comparison&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://medium.com/@pupapaik&#34; target=&#34;_blank&#34;&gt;Jakub Pavlík&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果你正在运行 Kubernetes，你可能正在使用，或者准备使用动态供给的块存储&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/#types-of-volumes&#34; target=&#34;_blank&#34;&gt;卷&lt;/a&gt;，而首当其冲的问题就是为集群选择合适的存储技术。这个事情并不能用一个简单的测试来做出简单的回答，告诉你目前市面上最好的技术是什么。存储技术的选择过程中，集群上运行的负载类型是一个重要的输入。对于裸金属集群来说，需要根据实际用例进行选择，并集成到自己的硬件之中。公有云中的托管 K8s，例如 AKS、EKS 或者 GKE，都具有开箱可用的块存储能力，然而这也不见得就是最好的选择。有很多因素需要考虑，比如说公有云的 StorageClass 的故障转移时间太长。例如在 一个针对 AWS EBS 的故障测试中，加载了卷的 Pod 用了&lt;a href=&#34;https://blog.rook.io/run-your-own-high-performance-ebs-wherever-kubernetes-runs-798a136bd808&#34; target=&#34;_blank&#34;&gt;超过五分钟&lt;/a&gt;才成功的在另一个节点上启动。Portworx 或者 OpenEBS 这样的云原生存储产品，正在尝试解决这类问题。&lt;/p&gt;

&lt;p&gt;本文的目标是使用最常见的 Kubernetes 存储方案，进行基本的性能对比。我觉得在 Azure AKS 上使用下列后端：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AKS 原生 Storageclass：Azure native premium
&lt;!-- - AKS 存储 --&gt;&lt;/li&gt;
&lt;li&gt;使用 cStor 后端的 OpenEBS&lt;/li&gt;
&lt;li&gt;Portworx&lt;/li&gt;
&lt;li&gt;Heketi 管理的 Gluster&lt;/li&gt;
&lt;li&gt;Rook 管理的 Ceph&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;现在我们来介绍每种存储后端，并交代一下安装过程，然后进入 AKS 测试环境进行测试，最后得出结果。&lt;/p&gt;

&lt;h2 id=&#34;存储&#34;&gt;存储&lt;/h2&gt;

&lt;p&gt;这一节中介绍测试中用到的存储方案，包含安装过程以及该方案的优缺点。&lt;/p&gt;

&lt;h3 id=&#34;azure-原生-storageclass&#34;&gt;Azure 原生 StorageClass&lt;/h3&gt;

&lt;p&gt;我选择这一方案的动机是以此作为所有测试的基线。这个方案&lt;strong&gt;应该&lt;/strong&gt;提供最佳性能。Azure 动态的创建托管磁盘，并把它们映射到 K8s 的虚拟机中，最终成为 Pod 的存储卷。&lt;/p&gt;

&lt;p&gt;这个方案很方便，什么多余的步骤都不需要。创建一个新的 AKS 集群之后，就自动提供了两个预定义的 StorageClass，分别是 &lt;code&gt;default&lt;/code&gt; 和 &lt;code&gt;managed-premium&lt;/code&gt;，premium 使用的是基于 SSD 的高性能低延迟磁盘。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get storageclasses
NAME                PROVISIONER                AGE
default (default)   kubernetes.io/azure-disk   8m
managed-premium     kubernetes.io/azure-disk   8m

$ kubectl get pvc
NAME              STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
dbench-pv-claim   Bound     pvc-e7bd34a4-1dbd-11e9-8726-ae508476e8ad   1000Gi     RWO            managed-premium   10s

$ kubectl get po
NAME           READY     STATUS              RESTARTS   AGE
dbench-w7nqf   0/1       ContainerCreating   0          29s
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;优点&#34;&gt;优点&lt;/h4&gt;

&lt;p&gt;AKS 开箱即用。&lt;/p&gt;

&lt;h4 id=&#34;缺点&#34;&gt;缺点&lt;/h4&gt;

&lt;p&gt;故障转移非常缓慢，有时需要十分钟以后，存储卷才能重新挂载到不同节点上的 Pod 里。&lt;/p&gt;

&lt;h3 id=&#34;openebs&#34;&gt;OpenEBS&lt;/h3&gt;

&lt;p&gt;对我来说 OpenEBS 是个全新事物，因此我很有兴趣做他的测试。他提出了一个新的 &lt;a href=&#34;https://docs.openebs.io/docs/next/conceptscas.html#advantages-of-cas&#34; target=&#34;_blank&#34;&gt;Container Attached Storage（容器挂载存储）&lt;/a&gt;概念，这是一个基于微服务的存储控制器，以及多个基于微服务的存储副本。他和 Portworx 同样，属于云原生存储分类的成员。&lt;/p&gt;

&lt;p&gt;它是一个完全开源的方案，目前提供两种后端——Jiva 和 cStor。我最开始选择的是 Jiva，后来切换到 cStor。cStor 有很多长处，例如他的控制器和副本被部署到单一的 OpenEBS 所在的命名空间之中，能够管理原始磁盘等。每个 K8s 卷都有自己的存储控制器，能在节点存储容量的许可范围内对存储进行扩展。&lt;/p&gt;

&lt;h4 id=&#34;在-aks-上运行&#34;&gt;在 AKS 上运行&lt;/h4&gt;

&lt;p&gt;在 AKS 上的安装非常容易。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;连接到所有 K8s 节点上，安装 iSCSI，这是因为他需要使用 iSCSI 协议在 K8s 节点之间进行 Pod 和控制器的连接。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;apt-get update
apt install -y open-iscsi
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使用一个 YAML 定义在 K8s 集群上完成部署：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;kubectl apply -f https://openebs.github.io/charts/openebs-operator-0.8.0.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;下一步，OpenEBS 控制器发现了节点中的所有磁盘。但是我必须手工标识出我附加的 AWS 托管磁盘。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get disk
NAME                                      AGE
disk-184d99015253054c48c4aa3f17d137b1     5m
disk-2f6bced7ba9b2be230ca5138fd0b07f1     5m
disk-806d3e77dd2e38f188fdaf9c46020bdc     5m
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;然后把这些磁盘加入 StoragePoolClaim，这个对象会在 StorageClass 中进行引用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: openebs-custom
  annotations:
    openebs.io/cas-type: cstor
    cas.openebs.io/config: |
      - name: StoragePoolClaim
        value: &amp;quot;cstor-disk&amp;quot;
provisioner: openebs.io/provisioner-iscsi
---
apiVersion: openebs.io/v1alpha1
kind: StoragePoolClaim
metadata:
  name: cstor-disk
spec:
  name: cstor-disk
  type: disk
  maxPools: 3
  poolSpec:
    poolType: striped
  disks:
    diskList:
    - disk-2f6bced7ba9b2be230ca5138fd0b07f1
    - disk-806d3e77dd2e38f188fdaf9c46020bdc
    - disk-184d99015253054c48c4aa3f17d137b1
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;完成这些步骤之后，就可以用 K8s 的 PVC 来动态的创建存储卷了。&lt;/p&gt;

&lt;h4 id=&#34;优点-1&#34;&gt;优点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;开源&lt;/li&gt;
&lt;li&gt;Maya 在资源使用的可视化方面做得非常好。可以在 K8s 中部署多个服务，方便的为集群的各方面数据设置监控和日志。对于排错工作来说，这十分重要。&lt;/li&gt;
&lt;li&gt;CAS 概念：我非常欣赏这一概念，我相信这是未来的趋势。&lt;/li&gt;
&lt;li&gt;OpenEBS 社区：在社区中我的任何问题都能在几分钟内得到解决。Slack 上的团队非常有帮助。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;缺点-1&#34;&gt;缺点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;不成熟：OpenEBS 还很年轻，目前还没有发布稳定版。核心团队还在进行后端的优化，未来几个月里会对性能做出很大提升。&lt;/li&gt;
&lt;li&gt;Kubelet 和存储控制器之间的 iSCSI 连接是通过 K8s Service 进行的，这在 Tungsten Fabric 之类的 CNI 插件环境中可能会出问题。&lt;/li&gt;
&lt;li&gt;需要在 K8s 节点上安装额外的软件（iSCSI），这对于托管集群来说非常不便。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注：OpenEBS 团队对我的案例场景进行了调整：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/kmova/openebs/tree/fio-perf-tests/k8s/demo/dbench&#34; target=&#34;_blank&#34;&gt;https://github.com/kmova/openebs/tree/fio-perf-tests/k8s/demo/dbench&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;portworx&#34;&gt;Portworx&lt;/h3&gt;

&lt;p&gt;Portworx 是另一个面向 Kubernetes 的容器原生存储方案，它专注于高度分布式的环境。这是一个主机可寻址的存储，每个卷都直接映射到挂在的主机上。他提供了基于应用 I/O 类型的自动微调能力。&lt;a href=&#34;https://portworx.com/makes-portworx-unique/&#34; target=&#34;_blank&#34;&gt;官方网站&lt;/a&gt;提供了更多信息。不幸的是，它也是本文中唯一的非开源产品。然而它提供了 3 节点的免费试用。&lt;/p&gt;

&lt;h4 id=&#34;在-aks-上运行-1&#34;&gt;在 AKS 上运行&lt;/h4&gt;

&lt;p&gt;在 AKS 上的安装同样简单，我用了他们&lt;a href=&#34;https://docs.portworx.com/portworx-install-with-kubernetes/cloud/azure/aks/2-deploy-px/#generate-the-specs&#34; target=&#34;_blank&#34;&gt;网站&lt;/a&gt;提供的生成器。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;选择基于 Portworx 的 ETCD，指定 K8s 版本为 1.11.4。&lt;/li&gt;
&lt;li&gt;因为我用了 Azure CNI，因此必须把数据网卡设置为 &lt;code&gt;azure0&lt;/code&gt;。否则 Portworx 会使用 docker bridge 的 IP 地址，而非 VM 网卡。&lt;/li&gt;
&lt;li&gt;最后网站会生成渲染完成的 YAML 文件。&lt;/li&gt;

&lt;li&gt;&lt;p&gt;提交后，会看到节点上运行的 Portworx Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods -o wide -n kube-system -l name=portworx
NAME             READY     STATUS    RESTARTS   AGE       IP          NODE                       NOMINATED NODE
portworx-g9csq   1/1       Running   0          14m       10.0.1.66   aks-agentpool-20273348-2   &amp;lt;none&amp;gt;
portworx-nt2lq   1/1       Running   0          14m       10.0.1.4    aks-agentpool-20273348-0   &amp;lt;none&amp;gt;
portworx-wcjnx   1/1       Running   0          14m       10.0.1.35   aks-agentpool-20273348-1   &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为 PVC 创建一个 StorageClass，定义高优先级，以及三个副本：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;root@aks-agentpool-20273348-0:~# kubectl get storageclass -o yaml portworx-sc
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  creationTimestamp: 2019-01-28T21:10:28Z
  name: portworx-sc
  resourceVersion: &amp;quot;55332&amp;quot;
  selfLink: /apis/storage.k8s.io/v1/storageclasses/portworx-sc
  uid: 23455e40-2341-11e9-bfcb-a23b1ec87092
parameters:
  priority_io: high
  repl: &amp;quot;3&amp;quot;
provisioner: kubernetes.io/portworx-volume
reclaimPolicy: Delete
volumeBindingMode: Immediate
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;优点-2&#34;&gt;优点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;部署方便：生成器包含配置细节。&lt;/li&gt;
&lt;li&gt;不像 Ceph 和 Glusterfs 那样需要进行额外配置。&lt;/li&gt;
&lt;li&gt;云原生存储：公有云和裸金属都可以运行。&lt;/li&gt;
&lt;li&gt;存储级别感知和应用感知的 I/O 微调。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;缺点-2&#34;&gt;缺点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;闭源：商业解决方案&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;glusterfs-heketi&#34;&gt;GlusterFS Heketi&lt;/h3&gt;

&lt;p&gt;GlusterFS 是知名的开源存储方案，是由 Redhat 提供的开源存储方案。&lt;a href=&#34;https://github.com/gluster/gluster-kubernetes#glusterfs-native-storage-service-for-kubernetes&#34; target=&#34;_blank&#34;&gt;Heketi&lt;/a&gt; 是 GlusterFS 的 RESTful 卷管理界面。它提供了易用的方式为 GlusterFS 卷提供了动态供给的功能。如果没有 Heketi 的辅助，就只能手工创建 GlusterFS 卷并映射到 K8s PV 了。关于 GlusterFS 的更多信息，请阅读&lt;a href=&#34;https://docs.gluster.org/en/latest/Install-Guide/Overview/&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;。&lt;/p&gt;

&lt;h4 id=&#34;在-aks-上运行-2&#34;&gt;在 AKS 上运行&lt;/h4&gt;

&lt;p&gt;根据 Heketi 的&lt;a href=&#34;https://github.com/gluster/gluster-kubernetes/blob/master/docs/setup-guide.md#deployment&#34; target=&#34;_blank&#34;&gt;快速入门&lt;/a&gt;文档进行部署。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;参照&lt;a href=&#34;https://github.com/gluster/gluster-kubernetes/blob/master/deploy/topology.json.sample&#34; target=&#34;_blank&#34;&gt;样例&lt;/a&gt;，创建一个包含磁盘和主机名的拓扑文件。&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Heketi 主要的开发和测试都在基于 RHEL 的操作系统上，我在 AKS 上使用 Ubuntu 主机时，出现了内核模块路径错误的问题，我提交了一个 &lt;a href=&#34;https://github.com/gluster/gluster-kubernetes/pull/557&#34; target=&#34;_blank&#34;&gt;PR&lt;/a&gt; 来修正这个问题。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;+++ b/deploy/kube-templates/glusterfs-daemonset.yaml
@@ -67,7 +67,7 @@ spec:
           mountPath: &amp;quot;/etc/ssl&amp;quot;
           readOnly: true
         - name: kernel-modules
-          mountPath: &amp;quot;/usr/lib/modules&amp;quot;
+          mountPath: &amp;quot;/lib/modules&amp;quot;
           readOnly: true
         securityContext:
           capabilities: {}
@@ -131,4 +131,4 @@ spec:
           path: &amp;quot;/etc/ssl&amp;quot;
       - name: kernel-modules
         hostPath:
-          path: &amp;quot;/usr/lib/modules&amp;quot;
+          path: &amp;quot;/lib/modules&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;我在 AKS 环境中遇到的另一个问题是一个非空磁盘，所以我用 &lt;code&gt;wipefs&lt;/code&gt; 为 glusterfs 进行清理。这个磁盘并未用过。&lt;/p&gt;

&lt;p&gt;$ wipefs -a /dev/sdc
/dev/sdc: 8 bytes were erased at offset 0x00000218 (LVM2_member): 4c 56 4d 32 20 30 30 31&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;最后运行 &lt;code&gt;gk-deploy -g -t topology.json&lt;/code&gt;，会在每个节点上运行 Heketi 控制器管理之下的 GlusterFS Pod。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get po -o wide
NAME                     READY   STATUS    RESTARTS IP        NODE                       NOMINATED NODE
glusterfs-fgc8f          1/1     Running   0       10.0.1.35  aks-agentpool-20273348-1
glusterfs-g8ht6          1/1     Running   0       10.0.1.4   aks-agentpool-20273348-0
glusterfs-wpzzp          1/1     Running   0       10.0.1.66  aks-agentpool-20273348-2
heketi-86f98754c-n8qfb   1/1     Running   0       10.0.1.69  aks-agentpool-20273348-2
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;然后我遇到了新问题。K8s 控制面无法使用 Heketi 的 &lt;code&gt;restURL&lt;/code&gt;。我测试了一下 kube dns 的记录，pod IP 和 svc IP 都没有生效。最后只能手工使用 Heketi CLI 来创建存储卷。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ export HEKETI_CLI_SERVER=http://10.0.1.69:8080
$ heketi-cli volume create --size=10 --persistent-volume --persistent-volume-endpoint=heketi-storage-endpoints | kubectl create -f -
persistentvolume/glusterfs-efb3b155 created

$ kubectl get pv
NAME                 CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
glusterfs-efb3b155   10Gi       RWX            Retain           Available
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后把现存 PV 映射为 PVC，加载给测试工具进行测试。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: glusterfs-efb3b155
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: &amp;quot;&amp;quot;
  resources:
    requests:
      storage: 10Gi
  volumeName: glusterfs-efb3b155
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pvc
NAME                 STATUS    VOLUME               CAPACITY   ACCESS MODES   STORAGECLASS   AGE
glusterfs-efb3b155   Bound     glusterfs-efb3b155   10Gi       RWX                           36m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Heketi 的更多输出：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ gluster volume info vol_efb3b15529aa9aba889d7900f0ce9849

Volume Name: vol_efb3b15529aa9aba889d7900f0ce9849
Type: Replicate
Volume ID: 96fde36b-e389-4dbe-887b-baae32789436
Status: Started
Snapshot Count: 0
Number of Bricks: 1 x 3 = 3
Transport-type: tcp
Bricks:
Brick1: 10.0.1.66:/var/lib/heketi/mounts/vg_5413895eade683e1ca035760c1e0ffd0/brick_cd7c419bc4f4ff38bbc100c6d7b93605/brick
Brick2: 10.0.1.35:/var/lib/heketi/mounts/vg_3277c6764dbce56b5a01426088901f6d/brick_6cbd74e9bed4758110c67cfe4d4edb53/brick
Brick3: 10.0.1.4:/var/lib/heketi/mounts/vg_29d6152eeafc57a707bef56f091afe44/brick_4856d63b721d794e7a4cbb4a6f048d96/brick
Options Reconfigured:
transport.address-family: inet
nfs.disable: on
performance.client-io-threads: off

$ kubectl get svc
NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
heketi                     ClusterIP   192.168.101.75   &amp;lt;none&amp;gt;        8080/TCP   5h
heketi-storage-endpoints   ClusterIP   192.168.103.66   &amp;lt;none&amp;gt;        1/TCP      5h

$ kubectl get endpoints
NAME                       ENDPOINTS                            AGE
heketi                     10.0.1.69:8080                       5h
heketi-storage-endpoints   10.0.1.35:1,10.0.1.4:1,10.0.1.66:1   5h
kubernetes                 172.31.22.152:443                    1d
root@aks-agentpool-20273348-0:~# kubectl get endpoints heketi-storage-endpoints -o yaml
apiVersion: v1
kind: Endpoints
metadata:
  creationTimestamp: 2019-01-29T15:14:28Z
  name: heketi-storage-endpoints
  namespace: default
  resourceVersion: &amp;quot;142212&amp;quot;
  selfLink: /api/v1/namespaces/default/endpoints/heketi-storage-endpoints
  uid: 91f802eb-23d8-11e9-bfcb-a23b1ec87092
subsets:
- addresses:
  - ip: 10.0.1.35
  - ip: 10.0.1.4
  - ip: 10.0.1.66
  ports:
  - port: 1
    protocol: TCP
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;优点-3&#34;&gt;优点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;久经考验的存储方案。&lt;/li&gt;
&lt;li&gt;比 Ceph 轻量。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;缺点-3&#34;&gt;缺点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Heketi 在公有云上表现不佳。在私有云上表现良好，安装会方便一些。&lt;/li&gt;
&lt;li&gt;并非为结构化数据设计，例如 SQL 数据库。然而可以使用 GlusterFS 为数据库提供备份和恢复支持。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;ceph-rook&#34;&gt;Ceph Rook&lt;/h3&gt;

&lt;p&gt;我在 OpenStack 私有云上尝试过安装和运行 Ceph。它需要为特定硬件定制参数，根据数据类型设计 pg 组、SSD 分区和 CRUSH 图等。所以第一次听说在 3 节点的 K8s 集群上运行 Ceph 的时候，我不太相信它能工作。结果 Rook 的编排工具让我印象深刻，它把所有的步骤和 K8s 的编排能力结合在一起，让安装变得非常简便。&lt;/p&gt;

&lt;h4 id=&#34;在-aks-上运行-3&#34;&gt;在 AKS 上运行&lt;/h4&gt;

&lt;p&gt;Rook 的缺省安装无需任何特定步骤，如果没什么高级配置，会非常简单。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;我使用的是 &lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/ceph-quickstart.md#ceph-storage-quickstart&#34; target=&#34;_blank&#34;&gt;Ceph 快速入门指南&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;为 AKS 配置 &lt;code&gt;FLEXVOLUME_DIR_PATH&lt;/code&gt;，这是因为它需要 &lt;code&gt;/etc/kubernetes/volumeplugins/&lt;/code&gt;，而不是 Ubuntu 中缺省的 &lt;code&gt;/usr/libexec&lt;/code&gt;，没有这个步骤，Kubelet 就&lt;a href=&#34;https://github.com/rook/rook/issues/1790#issuecomment-402574647&#34; target=&#34;_blank&#34;&gt;无法加载 PVC&lt;/a&gt; 了。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;diff --git a/cluster/examples/kubernetes/ceph/operator.yaml b/cluster/examples/kubernetes/ceph/operator.yaml
index 73cde2e..33f45c8 100755
--- a/cluster/examples/kubernetes/ceph/operator.yaml
+++ b/cluster/examples/kubernetes/ceph/operator.yaml
@@ -431,8 +431,8 @@ spec:
         # - name: AGENT_MOUNT_SECURITY_MODE
         #   value: &amp;quot;Any&amp;quot;
         # Set the path where the Rook agent can find the flex volumes
-        # - name: FLEXVOLUME_DIR_PATH
-        #  value: &amp;quot;&amp;lt;PathToFlexVolumes&amp;gt;&amp;quot;
+        - name: FLEXVOLUME_DIR_PATH
+          value: &amp;quot;/etc/kubernetes/volumeplugins&amp;quot;
         # Set the path where kernel modules can be found
         # - name: LIB_MODULES_DIR_PATH
         #  value: &amp;quot;&amp;lt;PathToLibModules&amp;gt;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;还要在 &lt;code&gt;deviceFilter&lt;/code&gt; 中指定要使用的设备，这里是 &lt;code&gt;/dev/sdc&lt;/code&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;diff --git a/cluster/examples/kubernetes/ceph/cluster.yaml b/cluster/examples/kubernetes/ceph/cluster.yaml
index 48cfeeb..0c91c48 100755
--- a/cluster/examples/kubernetes/ceph/cluster.yaml
+++ b/cluster/examples/kubernetes/ceph/cluster.yaml
@@ -227,7 +227,7 @@ spec:
   storage: # cluster level storage configuration and selection
     useAllNodes: true
     useAllDevices: false
-    deviceFilter:
+    deviceFilter: &amp;quot;^sdc&amp;quot;
     location:
     config:
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;安装之后，创建一个 Ceph block pool，以及 StorageClass，使用如下配置。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: rook-ceph-block
provisioner: ceph.rook.io/block
parameters:
  blockPool: replicapool
  clusterNamespace: rook-ceph
  fstype: xfs
reclaimPolicy: Retain
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;最后使用部署&lt;a href=&#34;https://github.com/rook/rook/blob/master/Documentation/ceph-toolbox.md&#34; target=&#34;_blank&#34;&gt;工具&lt;/a&gt;进行检查。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;ceph status
cluster:
id:     bee70a10-dce1-4725-9285-b9ec5d0c3a5e
health: HEALTH_OK

services:
mon: 3 daemons, quorum c,b,a
mgr: a(active)
osd: 3 osds: 3 up, 3 in

data:
pools:   0 pools, 0 pgs
objects: 0  objects, 0 B
usage:   3.0 GiB used, 3.0 TiB / 3.0 TiB avail
pgs:

[root@aks-agentpool-27654233-0 /]#
[root@aks-agentpool-27654233-0 /]#
[root@aks-agentpool-27654233-0 /]# ceph osd status
+----+--------------------------+-------+-------+--------+---------+--------+---------+-----------+
| id |           host           |  used | avail | wr ops | wr data | rd ops | rd data |   state   |
+----+--------------------------+-------+-------+--------+---------+--------+---------+-----------+
| 0  | aks-agentpool-27654233-0 | 1025M | 1021G |    0   |     0   |    0   |     0   | exists,up |
| 1  | aks-agentpool-27654233-1 | 1025M | 1021G |    0   |     0   |    0   |     0   | exists,up |
| 2  | aks-agentpool-27654233-2 | 1025M | 1021G |    0   |     0   |    0   |     0   | exists,up |
+----+--------------------------+-------+-------+--------+---------+--------+---------+-----------+
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&#34;优点-4&#34;&gt;优点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;在大型生产环境上的健壮存储系统。&lt;/li&gt;
&lt;li&gt;Rook 很好的简化了生命周期管理。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;缺点-4&#34;&gt;缺点&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;复杂：更加重量级，也不太适合在公有云上运行。在私有云上的运行可能更加合适。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;aks-测试环境&#34;&gt;AKS 测试环境&lt;/h2&gt;

&lt;p&gt;我用 3 个虚拟机创建了基本的 Azure AKS 集群。为了连接到 Premium SSD 上，我只能使用 type E 以上级别的虚拟机。因此我选择了 &lt;a href=&#34;https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-memory#esv3-series&#34; target=&#34;_blank&#34;&gt;Standard_E2s_v3&lt;/a&gt;，其上配备了 2 vCPU 以及 16GB 的内存。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/aks-vm-e2.png&#34; alt=&#34;vm&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在 AKS 集群所在的资源足中，可以看到所有的虚拟机、网络接口等资源。在这里创建 3 个 1TB 的 Premium SSD 存储，并手工挂载到每个虚拟机上。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/resource-group.png&#34; alt=&#34;resource group&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这样在每个实例上，我都有 1TB 的空磁盘。Azure 的页面上，根据我们选择的虚拟机和磁盘尺寸来看，性能应该有 5000 IOPS 以及 200MB/s 的吞吐量。最后一节会显示我们的真实结果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/azure-disk-performance.png&#34; alt=&#34;azure new disk&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;性能结果&#34;&gt;性能结果&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：每种存储的结果并不能作为独立的评估结果，但是其比较情况是可以参考的。有很多种对比测试的方法，这是最简单的一种。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;为了运行测试，我决定使用现成的测试工具 &lt;a href=&#34;https://github.com/logdna/dbench&#34; target=&#34;_blank&#34;&gt;Dbench&lt;/a&gt;，它是一个 k8s 的 YAML 文件，会使用 &lt;a href=&#34;https://github.com/axboe/fio&#34; target=&#34;_blank&#34;&gt;FIO&lt;/a&gt; 运行 8 个测试用例。可以在 Dockerfile 中&lt;a href=&#34;https://github.com/logdna/dbench/blob/master/docker-entrypoint.sh&#34; target=&#34;_blank&#34;&gt;指定不同测试&lt;/a&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;随机读写带宽。&lt;/li&gt;
&lt;li&gt;随机读写 IOPS。&lt;/li&gt;
&lt;li&gt;读写延迟。&lt;/li&gt;
&lt;li&gt;顺序读写。&lt;/li&gt;
&lt;li&gt;混合读写 IOPS。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所有测试的结果可以在 &lt;a href=&#34;https://gist.github.com/pupapaik/76c5b7f124dbb69080840f01bf71f924&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt; 上找到。&lt;/p&gt;

&lt;h3 id=&#34;随机读写带宽&#34;&gt;随机读写带宽&lt;/h3&gt;

&lt;p&gt;随机读写测试表明，GlusterFS、Ceph 以及 Portworx 的读取性能比 AWS 本地盘的 &lt;code&gt;hostPath&lt;/code&gt; 快了几倍。读缓存是罪魁祸首。GlusterFS 和 Portworx 的写入更快，其效率直逼本地磁盘。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/random-rw-bw.png&#34; alt=&#34;table1&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure1.png&#34; alt=&#34;figure1&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;随机读写-iops&#34;&gt;随机读写 IOPS&lt;/h3&gt;

&lt;p&gt;随机 IOPS 测试中，Portworx 和 Ceph 表现最好。Portworx 在写入方面获得了接近 Azure 原生 PVC 的 IOPS。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table2.png&#34; alt=&#34;table2&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure2.png&#34; alt=&#34;figure2&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;读写延迟&#34;&gt;读写延迟&lt;/h3&gt;

&lt;p&gt;延迟测试的结果比较有趣，Azure 原生 PVC 比多数其它存储都差。Portworx 和 Ceph 表现最好。写入方面，GlusterFS 要优于 Ceph。OpenEBS 的延迟相对来说非常的高。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table3.png&#34; alt=&#34;tables&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure3.png&#34; alt=&#34;figure&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;顺序读写&#34;&gt;顺序读写&lt;/h3&gt;

&lt;p&gt;顺序读写的结果和前面的随机测试差不多，然而 Cpeh 在读取方面比 GlusterFS 快了一倍多。写入结果基本一致，只有 OpenEBS 表现奇差。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table4.png&#34; alt=&#34;table4&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure4.png&#34; alt=&#34;figure4&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;混合读写-iops&#34;&gt;混合读写 IOPS&lt;/h3&gt;

&lt;p&gt;最后一个测试用例检查的是混合读写情况下的 IOPS，Portworx 和 Ceph 都给出了优于 Azure 原生 PVC 的结果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table5.png&#34; alt=&#34;table5&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/figure5.png&#34; alt=&#34;figure5&#34; /&gt;&lt;/p&gt;

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

&lt;p&gt;本文展示了一个简单的存储对比，使用未经性能优化的多种存储提供的存储卷进行测试和比较。建议关注本文所述方法，不建议直接采用结果进行判断。&lt;/p&gt;

&lt;p&gt;忽略 Azure 的原生 PVC 或 &lt;code&gt;hostPath&lt;/code&gt;，我们可以得出如下测试结果：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Portworx 是 AKS 上最快的容器存储。&lt;/li&gt;
&lt;li&gt;Ceph 是私有云集群上最快的开源存储后端。对公有云来说，其操作太过复杂，这些多余的复杂性并没有能提供更好的测试表现。&lt;/li&gt;
&lt;li&gt;OpenEBS 的概念很棒，但是其后端需要更多优化。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;调整性能数据的测试规模应该会很有意思。另外值得关注的对比就是 CPU 和内存的消耗。我会持续关注，并分享更多。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>kube-proxy 模式对比：iptables 还是 IPVS？</title>
      <link>/post/iptables-or-ipvs/</link>
      <pubDate>Thu, 02 May 2019 18:29:39 +0800</pubDate>
      <guid>/post/iptables-or-ipvs/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://www.tigera.io/blog/comparing-kube-proxy-modes-iptables-or-ipvs/&#34; target=&#34;_blank&#34;&gt;Comparing kube-proxy modes: iptables or IPVS?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://www.linkedin.com/in/alexpollitt&#34; target=&#34;_blank&#34;&gt;Alex Pollitt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;kube-proxy 是 Kubernetes 中的关键组件。他的角色就是在服务（ClusterIP 和 NodePort）和其后端 Pod 之间进行负载均衡。kube-proxy 有三种运行模式，每种都有不同的实现技术：&lt;strong&gt;userspace&lt;/strong&gt;、&lt;strong&gt;iptables&lt;/strong&gt; 或者 &lt;strong&gt;IPVS&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;userspace 模式非常陈旧、缓慢，已经不推荐使用。但是 iptables 和 IPVS 该如何选择呢？本文中我们会对这两种模式进行比较，看看他们在真正的微服务上下文中的表现，并解释在特定情况下的选择方法。&lt;/p&gt;

&lt;p&gt;首先我们说一下这两种模式的背景，然后开始测试并查看结果。&lt;/p&gt;

&lt;h2 id=&#34;背景-iptables-模式&#34;&gt;背景：iptables 模式&lt;/h2&gt;

&lt;p&gt;iptables 是一个 Linux 内核功能，是一个高效的防火墙，并提供了大量的数据包处理和过滤方面的能力。它可以在核心数据包处理管线上用 Hook 挂接一系列的规则。iptables 模式中 kube-proxy 在 &lt;code&gt;NAT pre-routing&lt;/code&gt; Hook 中实现它的 NAT 和负载均衡功能。这种方法简单有效，依赖于成熟的内核功能，并且能够和其它跟 iptables 协作的应用（例如 Calico）融洽相处。&lt;/p&gt;

&lt;p&gt;然而 kube-proxy 的用法是一种 O(n) 算法，其中的 n 随集群规模同步增长，这里的集群规模，更明确的说就是服务和后端 Pod 的数量。&lt;/p&gt;

&lt;h2 id=&#34;背景-ipvs-模式&#34;&gt;背景：IPVS 模式&lt;/h2&gt;

&lt;p&gt;IPVS 是一个用于负载均衡的 Linux 内核功能。IPVS 模式下，kube-proxy 使用 IPVS 负载均衡代替了 iptable。这种模式同样有效，IPVS 的设计就是用来为大量服务进行负载均衡的，它有一套优化过的 API，使用优化的查找算法，而不是简单的从列表中查找规则。&lt;/p&gt;

&lt;p&gt;这样一来，kube-proxy 在 IPVS 模式下，其连接过程的复杂度为 O(1)。换句话说，多数情况下，他的连接处理效率是和集群规模无关的。&lt;/p&gt;

&lt;p&gt;另外作为一个独立的负载均衡器，IPVS 包含了多种不同的负载均衡算法，例如轮询、最短期望延迟、最少连接以及各种哈希方法等。而 iptables 就只有一种随机平等的选择算法。&lt;/p&gt;

&lt;p&gt;IPVS 的一个潜在缺点就是，IPVS 处理数据包的路径和通常情况下 iptables 过滤器的路径是不同的。如果计划在有其他程序使用 iptables 的环境中使用 IPVS，需要进行一些研究，看看他们是否能够协调工作。（Calico 已经和 IPVS kube-proxy 兼容）&lt;/p&gt;

&lt;h2 id=&#34;性能对比&#34;&gt;性能对比&lt;/h2&gt;

&lt;p&gt;iptables 的连接处理算法复杂度是 O(n)，而 IPVS 模式是 O(1)，但是在微服务环境中，其具体表现如何呢？&lt;/p&gt;

&lt;p&gt;在多数场景中，有两个关键属性需要关注：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;响应时间：一个微服务向另一个微服务发起调用时，第一个微服务发送请求，并从第二个微服务中得到响应，中间消耗了多少时间？&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;CPU 消耗：运行微服务的过程中，总体 CPU 使用情况如何？包括用户和核心空间的 CPU 使用，包含所有用于支持微服务的进程（也包括 kube-proxy）。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为了说明问题，我们运行一个微服务作为客户端，这个微服务以 Pod 的形式运行在一个独立的节点上，每秒钟发出 1000 个请求，请求的目标是一个 Kubernetes 服务，这个服务由 10 个 Pod 作为后端，运行在其它的节点上。接下来我们在客户端节点上进行了测量，包括 iptables 以及 IPVS 模式，运行了数量不等的 Kubernetes 服务，每个服务都有 10 个 Pod，最大有 10,000 个服务（也就是 100,000 个 Pod）。我们用 golang 编写了一个简单的测试工具作为客户端，用标准的 NGINX 作为后端服务。&lt;/p&gt;

&lt;h2 id=&#34;响应时间&#34;&gt;响应时间&lt;/h2&gt;

&lt;p&gt;响应时间很重要，有助于我们理解连接和请求的差异。典型情况下，多数微服务都会使用持久或者 &lt;code&gt;keepalive&lt;/code&gt; 连接，这意味着每个连接都会被多个请求复用，而不是每个请求一次连接。这很重要，因为多数连接的新建过程都需要完成三次 TCP 握手的过程，这需要消耗时间，也需要在 Linux 网络栈中进行更多操作，也就会消耗更多 CPU 和时间。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/Picture1.png&#34; alt=&#34;Round-Trip Response TIme vs Number of Services&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这张图展示了两个关键点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;iptables 和 IPVS 的平均响应时间在 1000 个服务（10000 个 Pod）以上时，会开始观察到差异。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;只有在每次请求都发起新连接的情况下，两种模式的差异才比较明显。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不管是 iptables 还是 IPVS，kube-proxy 的响应时间开销都是和建立连接的数量相关的，而不是数据包或者请求数量，这是因为 Linux 使用了 Conntrack，能够高效地将数据包和现存连接关联起来。如果数据包能够被 Conntrack 成功匹配，那就不需要通过 kube-proxy 的 iptables 或 IPVS 规则来推算去向。Linux conntrack 非常棒！（绝大多数时候）&lt;/p&gt;

&lt;p&gt;值得注意的是，例子中的服务端微服务使用 NGINX 提供一个静态小页面。多数微服务要做更多操作，因此会产生更高的响应时间，也就是 kube-proxy 处理过程在总体时间中的占比会减少。&lt;/p&gt;

&lt;p&gt;还有个需要解释的古怪问题：既然 IPVS 的连接过程复杂度是 O(1)，为什么在 10,000 服务的情况下，非 Keepalive 的响应时间还是提高了？我们需要深入挖掘更多内容才能解释这一问题，但是其中一个因素就是因为上升的 CPU 用量拖慢了整个系统。这就是下一个主题需要探究的内容。&lt;/p&gt;

&lt;h2 id=&#34;cpu-用量&#34;&gt;CPU 用量&lt;/h2&gt;

&lt;p&gt;为了描述 CPU 用量，下图关注的是最差情况：不使用持久/keepalive 连接的情况下，kube-proxy 会有最大的处理开销。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/Picture2.png&#34; alt=&#34;Total CPU&#34; /&gt;&lt;/p&gt;

&lt;p&gt;上图说明了两件事：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在超过 1000 个服务（也就是 10,000 个 Pod）的情况下，CPU 用量差异才开始明显。&lt;/li&gt;
&lt;li&gt;在一万个服务的情况下（十万个后端 Pod），iptables 模式增长了 0.35 个核心的占用，而 IPVS 模式仅增长了 8%。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有两个主要因素造成 CPU 用量增长：&lt;/p&gt;

&lt;p&gt;第一个因素是，缺省情况下 kube-proxy 每 30 秒会用所有服务对内核重新编程。这也解释了为什么 IPVS 模式下，新建连接的 O(1) 复杂度也仍然会产生更多的 CPU 占用。另外，如果是旧版本内核，重新编程 iptables 的 API 会更慢。所以如果你用的内核较旧，iptables 模式可能会占用更多的 CPU。&lt;/p&gt;

&lt;p&gt;另一个因素是，kube-proxy 使用 IPVS 或者 iptables 处理新连接的消耗。对 iptables 来说，通常是 O(n) 的复杂度。在存在大量服务的情况下，会出现显著的 CPU 占用升高。例如在 10,000 服务（100,000 个后端 Pod）的情况下，iptables 会为每个请求的每个连接处理大约 20000 条规则。如果使用 NINGX 缺省每连接 100 请求的 keepalive 设置，kube-proxy 的 iptables 规则执行次数会减少为 1%，会把 iptables 的 CPU 消耗降低到和 IPVS 类似的水平。&lt;/p&gt;

&lt;p&gt;客户端微服务会简单的丢弃响应内容。真实世界中自然会进行更多处理，也会造成更多的 CPU 消耗，但是不会影响 CPU 消耗随服务数量增长的事实。&lt;/p&gt;

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

&lt;p&gt;在超过 1000 服务的规模下，kube-proxy 的 IPVS 模式会有更好的性能表现。虽然可能有多种不同情况，但是通常来说，让微服务使用持久连接、运行现代内核，也能取得较好的效果。如果运行的内核较旧，或者无法使用持久连接，那么 IPVS 模式可能是个更好的选择。&lt;/p&gt;

&lt;p&gt;抛开性能问题不谈，IPVS 模式还有个好处就是具有更多的负载均衡算法可供选择。&lt;/p&gt;

&lt;p&gt;如果你还不确定 IPVS 是否合适，那就继续使用 iptables 模式好了。这种传统模式有大量的生产案例支撑，他是一个不完美的缺省选项。&lt;/p&gt;

&lt;h2 id=&#34;补充-calico-和-kube-proxy-的-iptables-比较&#34;&gt;补充：Calico 和 kube-proxy 的 iptables 比较&lt;/h2&gt;

&lt;p&gt;本文中我们看到，kube-proxy 中的 iptables 用法在大规模集群中可能会产生性能问题。有人问我 Calico 为什么没有类似的问题。答案是 Calico 中 kube-proxy 的用法是不同的。kube-proxy 使用了一个很长的规则链条，链条长度会随着集群规模而增长，Calico 使用的是一个很短的优化过的规则链，经由 ipsets 的加持，也具备了 O(1) 复杂度的查询能力。&lt;/p&gt;

&lt;p&gt;下图证明了这一观点，其中展示了每次连接过程中，kube-proxy 和 Calico 中 iptables 规则数量的平均值。这里假设集群中的节点平均有 30 个 Pod，每个 Pod 具有 3 个网络规则。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/Picture3.png&#34; alt=&#34;calico vs kube-proxy&#34; /&gt;&lt;/p&gt;

&lt;p&gt;即使是使用 10,000 个服务和 100,000 个 Pod 的情况下，Calico 每连接执行的 iptables 规则也只是和 kube-proxy 在 20 服务 200 个 Pod 的情况基本一致。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Linux Conntrack 的短板</title>
      <link>/post/when-linux-conntrack-is-no-longer-your-friend/</link>
      <pubDate>Thu, 02 May 2019 16:27:45 +0800</pubDate>
      <guid>/post/when-linux-conntrack-is-no-longer-your-friend/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://www.tigera.io/blog/when-linux-conntrack-is-no-longer-your-friend/&#34; target=&#34;_blank&#34;&gt;When Linux conntrack is no longer your friend&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://www.linkedin.com/in/alexpollitt&#34; target=&#34;_blank&#34;&gt;Alex Pollitt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Linux 内核中，&lt;code&gt;conntrack&lt;/code&gt; 的是网络栈的核心功能之一。它使得内核能够跟踪所有逻辑网络连接或者流量，因此能够鉴别构成流量的数据包，从而对每个数据流进行一致的处理。&lt;/p&gt;

&lt;p&gt;Conntrack 是一个重要的内核功能，是一些关键用例的基础：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;NAT 能够根据 Conntrack 的信息，对构成数据流的所有数据包进行翻译。例如当 Pod 访问 Kubernetes 服务的时候，kube-proxy 的负载均衡功能会使用 NAT 把连接重定向给指定的后端 Pod。Conntrack 负责对指定连接进行记录，发送到 Service IP 的数据包会被发送给同一个后端 Pod，从后端 Pod 返回的数据包会反向送回源 Pod。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Calico 这样的有状态防火墙，依赖 Conntrack 的信息来精确的将响应流量纳入白名单。用户如果编写了一个“允许 Pod 连接所有远端 IP”的策略，无需进行其它工作就能够允许所有的返回流量（如果没有这种功能，就必须加入一个不那么安全的规则：允许所有目标是该 Pod 的流量）。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外 Conntrack 还能够提高性能（降低 CPU 和延迟），这是因为只有第一个数据包需要完成整个网络栈的处理，参见 &lt;a href=&#34;https://www.tigera.io/blog/comparing-kube-proxy-modes-iptables-or-ipvs/&#34; target=&#34;_blank&#34;&gt;Comparing kube-proxy modes&lt;/a&gt; 一文，其中包含了这方面的例子。&lt;/p&gt;

&lt;p&gt;然而 Conntrack 也有其限制。&lt;/p&gt;

&lt;h2 id=&#34;那么它哪里不行了&#34;&gt;那么它哪里不行了？&lt;/h2&gt;

&lt;p&gt;Conntrack 的存储表有一个可配置的最大容量，如果满了，连接通常会被拒绝和丢弃。在多数负载情况下，这个表的容量是足够的，不会出现这种问题。但是有些场景下，Conntrack 也会不够用：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;最常见的情况是，如果你的服务器需要同时处理非常大数量的活动连接。例如你的 Conntrack 表设置为 128k 的容量，但是你的并发连接超过了 128k，这肯定会出问题。&lt;/li&gt;
&lt;li&gt;另外一种情况没那么常见——每秒处理极大数量的连接。这些连接就算再短，Linux 还会在超时（通常是 120 秒）期限内对其进行跟踪。例如如果你的 Conntrack 容量设置为 128k，尝试每秒钟处理 1100 个连接，这就会超出 Conntrack 表的限制（128k/120秒 = 1092 连接/秒）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有个别的负载类型就符合这种条件。另外如果在一个恶劣环境中，用大量的半开连接冲击服务器，就能造成拒绝服务攻击的效果。两种情况下，Conntrack 都会成为系统中的瓶颈。有些情况下，通过提高 Conntrack 数据表容量或者降低 Conntrack 的超时时间（如果调节失误，可能会造成更多痛苦）就能解决问题。别的场景中，可能需要跨过 Conntrack 来处理这种威胁。&lt;/p&gt;

&lt;h2 id=&#34;一个真实的案例&#34;&gt;一个真实的案例&lt;/h2&gt;

&lt;p&gt;我们合作的某大型 SaaS 供应商，他们有一组运行于物理机（不是虚拟化，也不是容器化）上的 Memcached 服务器，每台服务器每秒都要处理 50k 以上的短连接。这可不是标准的 Linux 配置能够承受的。&lt;/p&gt;

&lt;p&gt;他们曾经使用提高 Conntrack 数据表容量和降低超时时间的方式进行调整，但是这种调整非常脆弱，内存占用增长极大（GB 级），超时时间过短让 Conntrack 的益处（降低 CPU 和数据包延迟）也大为减少。&lt;/p&gt;

&lt;p&gt;因此他们转向了 Calico，Calico 的网络策略允许指定部分流量绕过 Conntrack。这一措施让他们得到了期待的性能，并且还得到了 Calico 带来的安全优势。&lt;/p&gt;

&lt;h2 id=&#34;绕开-conntrack-的妥协之处&#34;&gt;绕开 Conntrack 的妥协之处&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Do-not-track&lt;/code&gt; 策略通常是对称的。上面谈到的 SaaS 供应商案例，他们的工作负载是内部的，因此他们可以非常严格的在工作负载和 Memcached 之间进行白名单设置。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;Do-not-track&lt;/code&gt; 策略是不知道连接的方向的。所以只要 Memcached 服务器知道 Memcached 客户端的源端口，他都可以尝试连接。但是如果为 Memcached 客户端定义了正确的策略，那么还可以在客户端拒绝这些连接。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;Do-not-track&lt;/code&gt; 对每个数据包生效，而通常的网络策略只对数据流中的第一个数据包生效。这样会提高 CPU 的消耗。但是在短连接环境下，网络策略造成的消耗还是低于 &lt;code&gt;Conntrack&lt;/code&gt; 过程的消耗的。例如 SaaS 供应商的例子中，每连接中的数据包都很少，所以使用策略处理每个数据包的过程中造成的多余开销也就可以接受了。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;测试一下&#34;&gt;测试一下&lt;/h2&gt;

&lt;p&gt;我们测试了单一 Memcached 服务器 Pod 以及运行在远程节点上的多个客户端的场景，这种场景能方便的产生大量链接。Memcached Pod 所在的节点有 8 个 CPU 核心，Conntrack 表容量为 512k（主机的标准设置）。我们在几种方案中进行了比较：没有网络策略；Calico 通用网络策略；Calico &lt;code&gt;Do-not-track&lt;/code&gt; 策略。&lt;/p&gt;

&lt;p&gt;第一个测试中，我们限制每秒 4000 个连接，以此来方便的观察 CPU 方面的差异。无策略和正常策略的测试用例，其结果没有差异，而 &lt;code&gt;Do-not-track&lt;/code&gt; 策略降低了 20%。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/Picture2-1.png&#34; alt=&#34;CPU&#34; /&gt;&lt;/p&gt;

&lt;p&gt;第二个测试中，我们尽量的提高连接数量，这样就可以观察到 Memcached 能够处理的每秒最大连接数量了。如前所述，无策略和普通策略的情况里，受到 Conntrack 数据表容量的影响，只能达到每秒 4000 多连接的能力（512k / 120s = 4,369 connections/s）。而 &lt;code&gt;Do-not-track&lt;/code&gt; 策略下，连接数达到了 60k 每秒，没有出现问题。我们相信，可以通过更多的客户端来产生更高的负载数量，但是这一数字已经证实了我们提到的观点了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/Picture1-1.png&#34; alt=&#34;CONNECTION&#34; /&gt;&lt;/p&gt;

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

&lt;p&gt;Conntrack 是一个重要的内核功能。大多数场景下，它都能发挥很好的作用。然而有些小众场景中，Conntrack 的开销会大于其收益。在这种情况下，Calico 的网络策略可以通过选择性的绕过 Conntrack，并提高网络安全性。而对其他流量，Conntrack 还是你的好朋友。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>如何参与 Kubernetes 文档的本地化工作</title>
      <link>/post/how-you-can-help-localize-kubernetes-docs/</link>
      <pubDate>Sun, 28 Apr 2019 08:05:15 +0800</pubDate>
      <guid>/post/how-you-can-help-localize-kubernetes-docs/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2019/04/26/how-you-can-help-localize-kubernetes-docs/&#34; target=&#34;_blank&#34;&gt;How You Can Help Localize Kubernetes Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://twitter.com/zachorsarah&#34; target=&#34;_blank&#34;&gt;Zach Corleissen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;去年我们对 Kubernetes 网站进行了优化，加入了&lt;a href=&#34;https://kubernetes.io/blog/2018/11/08/kubernetes-docs-updates-international-edition/&#34; target=&#34;_blank&#34;&gt;多语言内容的支持&lt;/a&gt;。贡献者们踊跃响应，加入了多种新的本地化内容：截至 2019 年 4 月，Kubernetes 文档有了 9 个不同语言的未完成版本，其中有 6 个是 2019 年加入的。在每个 Kubernetes 文档页面的上方，读者都可以看到一个语言选择器，其中列出了所有可用语言。&lt;/p&gt;

&lt;p&gt;不论是完成度最高的&lt;a href=&#34;https://v1-12.docs.kubernetes.io/zh/&#34; target=&#34;_blank&#34;&gt;中文版 v1.12&lt;/a&gt;，还是最新加入的&lt;a href=&#34;https://kubernetes.io/pt/&#34; target=&#34;_blank&#34;&gt;葡萄牙文版 v1.14&lt;/a&gt;，各语言的本地化内容还未完成，这是一个进行中的项目。如果读者有兴趣对现有本地化工作提供支持，请继续阅读。&lt;/p&gt;

&lt;h2 id=&#34;什么是本地化&#34;&gt;什么是本地化&lt;/h2&gt;

&lt;p&gt;翻译是以词表意的问题。而本地化在此基础之上，还包含了过程和设计方面的工作。&lt;/p&gt;

&lt;p&gt;本地化和翻译很像，但是包含更多内容。除了进行翻译之外，本地化还要为编写和发布过程的框架进行优化。例如，Kubernetes.io 多数的站点浏览功能（按钮文字）都保存在&lt;a href=&#34;https://github.com/kubernetes/website/tree/master/i18n&#34; target=&#34;_blank&#34;&gt;单独的文件&lt;/a&gt;之中。所以启动新本地化的过程中，需要包含加入对特定文件中字符串进行翻译的工作。&lt;/p&gt;

&lt;p&gt;本地化很重要，能够有效的降低 Kubernetes 的采纳和支持门槛。如果能用母语阅读 Kubernetes 文档，就能更轻松的开始使用 Kubernetes，并对其发展作出贡献。&lt;/p&gt;

&lt;h2 id=&#34;如何启动本地化工作&#34;&gt;如何启动本地化工作&lt;/h2&gt;

&lt;p&gt;不同语言的本地化工作都是单独的功能——和其它 Kubernetes 功能一致，贡献者们在一个 SIG 中进行本地化工作，分享出来进行评审，并加入项目。&lt;/p&gt;

&lt;p&gt;贡献者们在团队中进行内容的本地化工作。因为自己不能批准自己的 PR，所以一个本地化团队至少应该有两个人——例如意大利文的本地化团队有两个人。这个团队规模可能很大：中文团队有几十个成员。&lt;/p&gt;

&lt;p&gt;每个团队都有自己的工作流。有些团队手工完成所有的内容翻译；有些会使用带有翻译插件的编译器，并使用评审机来提供正确性的保障。SIG Docs 专注于输出的标准；这就给了本地化团队采用适合自己工作情况的工作流。这样一来，团队可以根据最佳实践进行协作，并以 Kubernetes 的社区精神进行分享。&lt;/p&gt;

&lt;h2 id=&#34;为本地化工作添砖加瓦&#34;&gt;为本地化工作添砖加瓦&lt;/h2&gt;

&lt;p&gt;如果你有兴趣为 Kubernetes 文档加入新语种的本地化内容，&lt;a href=&#34;https://kubernetes.io/docs/contribute/localization/&#34; target=&#34;_blank&#34;&gt;Kubernetes contribution guide&lt;/a&gt; 中包含了这方面的相关内容。&lt;/p&gt;

&lt;p&gt;已经启动的的本地化工作同样需要支持。如果有兴趣为现存项目做出贡献，可以加入本地化团队的 Slack 频道，去做个自我介绍。各团队的成员会帮助你开始工作。&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;语种&lt;/th&gt;
&lt;th&gt;Slack 频道&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;a href=&#34;https://kubernetes.slack.com/messages/CE3LNFYJ1/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-zh&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;英文&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/C1J0BPD2M/&#34; target=&#34;_blank&#34;&gt;#sig-docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;法文&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/CG838BFT9/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-fr&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;德文&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/CH4UJ2BAL/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-de&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;印地&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/CJ14B9BDJ/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-hi&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;印度尼西亚文&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/CJ1LUCUHM/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-id&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;意大利文&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/CGB1MCK7X/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-it&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;日文&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/CAG2M83S8/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-ja&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;韩文&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/CA1MMR86S/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-ko&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;葡萄牙文&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/CJ21AS0NA/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-pt&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;西班牙文&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://kubernetes.slack.com/messages/CH7GB2E3B/&#34; target=&#34;_blank&#34;&gt;#kubernetes-docs-es&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&#34;下一步&#34;&gt;下一步？&lt;/h2&gt;

&lt;p&gt;最新的&lt;a href=&#34;https://kubernetes.slack.com/messages/CJ14B9BDJ/&#34; target=&#34;_blank&#34;&gt;印地文本地化&lt;/a&gt;工作正在启动。为什么不加入你的语言？&lt;/p&gt;

&lt;p&gt;身为 SIG Docs 的主席，我甚至希望本地化工作跳出文档范畴，直接为 Kubernetes 组件提供本地化支持。有什么组件是你希望支持不同语言的么？可以提交一个 &lt;a href=&#34;https://github.com/kubernetes/enhancements/tree/master/keps&#34; target=&#34;_blank&#34;&gt;Kubernetes Enhancement Proposal&lt;/a&gt; 来促成这一进步。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>关于 Anthos- Google 的混合多云平台</title>
      <link>/post/everything-u-want-to-now-about-anthos/</link>
      <pubDate>Tue, 16 Apr 2019 16:17:22 +0800</pubDate>
      <guid>/post/everything-u-want-to-now-about-anthos/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://www.forbes.com/sites/janakirammsv/2019/04/14/everything-you-want-to-know-about-anthos-googles-hybrid-and-multi-cloud-platform/amp/&#34; target=&#34;_blank&#34;&gt;Everything You Want To Know About Anthos - Google&amp;rsquo;s Hybrid And Multi-Cloud Platform&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://www.forbes.com/sites/janakirammsv/&#34; target=&#34;_blank&#34;&gt;Janakiram MSV&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最近结束的 Cloud Next 会议上，Google 宣布企业级混合多云平台 &lt;a href=&#34;https://cloud.google.com/anthos/&#34; target=&#34;_blank&#34;&gt;Anthos&lt;/a&gt; 进入 GA 阶段。&lt;/p&gt;

&lt;p&gt;虽说会上有超过一百个发布，Anthos 依旧醒目。它标志着 Google 官方开始涉足企业数据中心。这是主流公有云厂商第一次以官方身份踏入多云平台的市场。&lt;/p&gt;

&lt;p&gt;从 Sundar Pichai 到 Thomas Kurian 再到 Urs Hölzle，Anthos 被描述为下一代技术。很明显，Google 的高层团队为该项目深感自豪。&lt;/p&gt;

&lt;p&gt;Anthos 和其它公有云服务不同。它不仅是一个产品，而是一个覆盖应用程序现代化、云迁移、混合云和多云管理等主题的家族品牌。&lt;/p&gt;

&lt;p&gt;虽然 Anthos 及其 GA 级别的成熟度被广泛报道，但是这一发布活动还是令人困惑的。文档非常稀少，相关服务并未完全集成到自助工作台之中。除了混合云的连接以及多云应用部署之外，Google 的这一新技术的更多细节并不为人所知。&lt;/p&gt;

&lt;h2 id=&#34;基础构成&#34;&gt;基础构成&lt;/h2&gt;

&lt;p&gt;Anthos 的核心是目前最流行的开源项目：Kubernetes。GKE 是 Google 云提供的容器即服务技术，Anthos正是构建于 GKE 的坚实基础之上。当然还有一些其它的重要技术为 Kubernetes 提供了必要的补充。&lt;/p&gt;

&lt;p&gt;镜头拉近，看看 Anthos 的核心组件：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;GKE：Anthos 的命令和控制核心。用户通过 GKE 的控制平面来对分散在 Google 云、私有数据中心一级其它云平台上的基础设施进行管理。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;GKE On-Prem：Google 推出了一个基于 Kubernetes 的和 GKE 一致的软件平台。用户能够在任何的兼容硬件上部署这一产品，而 Google 将会对其进行管理。从升级 Kubernetes 版本到应用最新补丁，Google 都视其为 GKE 的逻辑扩展。尤其需要注意的是 GKE On-Prem 运行在 VMWare vSphere 6.5 的虚拟化基础上，Hyper-V 和 KVM 等其它虚拟化技术的支持还在开发之中。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Istio：这一技术让跨平台的联邦网络管理成为可能。Anthos 需要为部署在不同数据中心、GCP 以及其它云上的多种应用程序的组件建立服务网格，Istio 自然是首选。它会和 VMWare NSX、Cisco ACI 以及 Google 自己的 Andromeda 等 SDN 进行无缝集成。已经在网络设施上（例如 F5） 进行投资的客户，可以将 Istio 和负载均衡及防火墙集成起来。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Velostrata：Google 在 2018 年收购了这一云迁移技术，来增强 Kubernetes 的竞争力。Velostrata 的主要功能——在 GCE 实例中复制物理机/虚拟机，并把现有虚拟机转换为 Kubernetes 应用（Pod）。这是业界首个物理机到 Kubernetes 的迁移工具，由 Google 提供。这一技术以 Anthos Migrate 的面目出现，目前是 Beta 阶段。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Anthos 配置管理：Kubernetes 是一个可扩展的策略驱动的平台。Anthos 的客户必须面对运行在不同环境中的多个 Kubernetes，因此 Google 尝试利用 Anthos 来简化配置管理工作。从发布工件、配置项目、网络策略、密文和密码等类型的配置，Anthos 配置管理都能够进行管理并将配置应用到一或多个集群之中。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Stackdriver：Stackdriver 为 Anthos 基础设施和应用提供了可观察性的支持。客户能够使用这一组件跟踪运行在 Anthos集群状态，以及部署在各个托管集群上的应用的健康情况。该组件负责集中地提供监控、日志、跟踪以及观察的支持。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;GCP Cloud Interconnect：在企业数据中心以及云基础设施之间的高速互联，是混合云平台的必要条件。Cloud Interconnect 能够在数据中心和云间交付高达 100Gbps 的高速网络。客户也可以使用 Equinix、NTT Communications、Softbanck 等电信厂商的网络将其数据中心延伸到 GCP。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;GCP Marketplace：Google 为能够在 Kubernetes 上运行的（来自 ISV 和开源的）软件列表。用户能够在 Anthos 中一键部署 Cassandra 数据库或者 GitLab 等软件。最终 Google 可能还会为内部 IT 提供一个私有的 Catalog 服务。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;应用程序-greenfield-vs-brownfield&#34;&gt;应用程序：Greenfield vs Brownfield&lt;/h2&gt;

&lt;p&gt;Anthos 的核心场景就是应用的现代化进程。Google 所展望的未来世界中，所有企业都运行在 Kubernetes 上。为了达成这一目标，Google 在 Velostrata 这样的技术上进行投资，从而完成从虚拟机到容器的就地升级。&lt;/p&gt;

&lt;p&gt;Google 为 VMware 的 vRealize 构建了一个插件，可以把现有虚拟机转换为 Kubernetes Pod。即使是 PostgreSQL 和 MySQL 这样的有状态应用，都能够转换为 StatefulSet 在 Kubernetes 中运行。&lt;/p&gt;

&lt;p&gt;在从自有虚拟机向云端虚拟机的迁移问题上，Google 表现得轻描淡写。但是 Velostrata 的原始服务都是基于 VM 的。客户的业务线应用程序，例如 SAP、Oracle Finacials 以及 Peoplesoft 都可以持续的运行在自家数据中心的虚拟机中，或者也可以选择迁移到 GCE 的虚拟机上。Anthos 在技术上提供虚拟机和 Kubernetes 容器化应用之间的互操作性。&lt;/p&gt;

&lt;p&gt;有了 Anthos，Google 想要拥有所有运行在 Kubernetes 之上现代微服务应用（Greenfield），同时也想将现有虚拟机（Brownfield）转换为容器。非 x86 架构中的存量应用会持续运行在物理或者虚拟机上。&lt;/p&gt;

&lt;h2 id=&#34;广泛的行业合作&#34;&gt;广泛的行业合作&lt;/h2&gt;

&lt;p&gt;因为缺乏与业内其它厂商的合作和伙伴关系，Google 经常受到批评。为了对抗微软和 Amazon，Google 准备和行业中的其它老牌企业进行合作。毫无疑问，这一举措会帮助 Google 在企业市场获得一席之地。&lt;/p&gt;

&lt;p&gt;为了让 Anthos 走向成功，Google 将不得不依靠现有的企业市场玩家进行推动。它要寻求的是和微软以及 Amazon 的合作伙伴，来一起推进混合云进程。&lt;/p&gt;

&lt;p&gt;Cisco 将 HyperFlex、ACI、SD-WAN 以及 Stealthwatch Cloud 扩展到 Anthos 之上，为其提供 SDN 能力。VMware 正在为 Pivotal Kubernetes Service（PKS）加入 Anthos 支持，另外还有 VeloCloud 提供的 SD-WAN 能力；它还将它的流行 SDN 方案 NSX 集成到了 Istio 之中。&lt;/p&gt;

&lt;p&gt;大量 ISV 正在启动对 Anthos 的支持。从 Citrix 到 NetApp 到 MongoDB，众多的平台供应商正在尝试将其软件集成到 Anthos 之中。&lt;/p&gt;

&lt;h2 id=&#34;google-的-kubernetes-攻势&#34;&gt;Google 的 Kubernetes 攻势&lt;/h2&gt;

&lt;p&gt;作为 Kubernetes 的创始人，毫无疑问的，Google 是很擅长管理容器的。在 Docker 开始展现出对开发者的吸引力之后，Google 意识到这是放养 Kubernetes 的绝佳机会。Google 也是业内第一个提供托管 Kubernetes 的公有云厂商。虽然有为数众多的竞争者，GKE 仍然是运行微服务的最佳平台。&lt;/p&gt;

&lt;p&gt;基于对 Kubernetes 的深入理解，以及在这方面的持续投入，Google 希望在容器和微服务的新世界之中占据主导地位。它希望企业能够跳出虚拟机，在 Kubernetes 上运行他们的现代应用。&lt;/p&gt;

&lt;p&gt;Anthos 是 Google 的重要一步。它正在冒险摆脱其对手用于吸引企业客户的混合云玩法。Anthos 对标的是微软的 Azure Stack 以及 AWS 提出的 VMware 结合 Outpost 的混合云方案。Google 和竞争对手的明显差别在于，它的方案根植于容器和 Kubernetes 技术。&lt;/p&gt;

&lt;p&gt;Google 希望利用在 Kubernetes 和云原生领域的领先地位。它迅速地围绕 Kubernetes 构建了企业战略。Google 意图通过 Anthos 成为容器生态系统中的 VMware。VMware 在企业中推动其 hypervisor 以及软件定义的基础设施的手段，正是现在 Google 的行为方式。&lt;/p&gt;

&lt;p&gt;但是微软同样在容器和 Kubernetes 方面下了重注。它通过将公有云中的旗舰级能力下放到私有云的方式，模糊了 Azure 和 Azure Stack 之间的界线。微软能够用 Azure Kubernetes 服务来复制 Anthos 的战术。Redmond 将如何使用 Azure Stack 玩转 Kubernetes，是个令人感兴趣的问题。&lt;/p&gt;

&lt;p&gt;如果一切如 Google 所愿，Anthos 最终会成为运行企业负载的首选平台。&lt;/p&gt;

&lt;h2 id=&#34;企业就绪&#34;&gt;企业就绪&lt;/h2&gt;

&lt;p&gt;让来自 Cisco 和 VMware 的高管和 Thomas Kurian 一起登台，这是来自 Google 的声明：已经为企业市场做好准备。这一表现当然会增强企业客户在 Anthos 和 GCP 上进行投入的信心。&lt;/p&gt;

&lt;p&gt;Kubernetes 被认为是一个高技术的、具有极客气息的平台，因此吸引了开发和运营人员。而有了 Anthos 的帮助，Google 将会改变客户对 Kubernetes 的刻板印象。它将 Kubernetes 变成一个可用的、可靠的企业级混合云平台。&lt;/p&gt;

&lt;p&gt;Thomas Kurian 掌舵之后，Google 开始关注企业市场。如果它能够用好合作伙伴关系，并持续和关键的行业参与者进行合作，Google 和 Anthos 将会给企业基础设施市场带来巨大冲击。&lt;/p&gt;

&lt;h2 id=&#34;云原生生态的巨大机会&#34;&gt;云原生生态的巨大机会&lt;/h2&gt;

&lt;p&gt;Anthos 提振了对云原生生态系统的信心。前面提到过，Google 希望成为 Kubernetes 世界中的 VMware。但是和 VMware 登上企业宝座的时候相比，世界已经不同了。&lt;/p&gt;

&lt;p&gt;最大的区别就是开源软件。Google 现在面临的环境中，软件已经不再是关键区别。&lt;/p&gt;

&lt;p&gt;为了 Anthos 的成功，Google 必须依赖社区和生态系统。为云原生环境提供利基产品的创业公司（例如 Tigera、Portworx、Robin、Confluent、Cloudbees）将会得到更好的机会，Anthos 的推出，会让其受益匪浅。&lt;/p&gt;

&lt;p&gt;从小型的本地供应商到埃森哲、高知特这样的全球 SI，供应商和集成商正在为 Anthos 做准备。Google 的混合云战略将转化为价值数百万没有的的服务机会。&lt;/p&gt;

&lt;p&gt;随着 Google 向企业宣传和销售 Kubernetes 的举措，创业公司将借势将其触角伸向企业。&lt;/p&gt;

&lt;p&gt;Google 在 Anthos 上的豪赌对行业大有裨益，开源社区和云原生生态系统将加速 Kubernetes 的采用过程。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>从 Metric Server 到 Kubelet 服务证书</title>
      <link>/post/from-metric-server/</link>
      <pubDate>Wed, 27 Feb 2019 21:30:31 +0800</pubDate>
      <guid>/post/from-metric-server/</guid>
      <description>&lt;p&gt;很少用 Kubeadm，一直用自有 CA 签发证书，所以 TLS Bootstrap 也极少接触，然后乐子就来了。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ git clone https://github.com/kubernetes-incubator/metrics-server.git
$ cd metrics-server/deploy/1.8+
$ kubectl apply -f .
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl top nodes&lt;/code&gt;，返回的永远都是 &lt;code&gt;error: metrics not available yet&lt;/code&gt;。&lt;code&gt;kubectl logs metrics-server-fc6d4999b-58xtc&lt;/code&gt; 查看日志，其中大量的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;unable to fetch metrics from Kubelet node-standard-3 (node-standard-3): Get https://node-standard-3:10250/stats/summary/: x509: certificate signed by unknown authority]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查一下，很明显，kubelet 提供的 https 服务使用了未经认可的 CA：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ openssl s_client -showcerts -connect node-standard-3:10250
...
    Verify return code: 19 (self signed certificate in certificate chain)
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Metric Server 支持一个参数 &lt;code&gt;--kubelet-insecure-tls&lt;/code&gt;，可以跳过这一检查，然而官方也明确说了，这种方式不推荐生产使用。&lt;/p&gt;

&lt;p&gt;这时候我又想到个问题，那 API Server 是怎么访问 Kubelet 的？最后我看到，API Server 中有一行注释：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// Proxying to pods and services is IP-based... don&#39;t expect to be able to verify the hostname
proxyTLSClientConfig := &amp;amp;tls.Config{InsecureSkipVerify: true}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;那么问题来了，如何让 Kubelet 具备一个“正式”的证书，让各种组件可以放心的使用 TLS 进行访问呢？查阅资料发现，目前的 kubeadm 流程中，kubelet 的 Bootstrap 因为节点动态的原因，已经不再自动完成 Kubelet 服务端点的证书签发了，使用统一 CA 自行签署，或者恢复 Bootstrap 中的服务证书申请流程，也就能完成任务了。&lt;/p&gt;

&lt;p&gt;Kubelet 的 &lt;code&gt;config.yaml&lt;/code&gt; 中加入一行：&lt;code&gt;serverTLSBootstrap: true&lt;/code&gt;，即可启动这一过程。重启 Kubelet，会发现出现了新的 CSR：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get csr
NAME        AGE     REQUESTOR                     CONDITION
csr-f29hk   5s      system:node:node-standard-2   Pending
csr-n9pvr   3m31s   system:node:node-standard-3   Pending
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果使用 &lt;code&gt;base64 -d&lt;/code&gt; 对 csr 的 request 字段做解码，并查看其请求内容的话，会发现：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ openssl req -in csr.pem -noout -text
...
X509v3 Subject Alternative Name:
                DNS:node-standard-2, IP Address:10.211.55.28
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;证书请求中已经带有了 SAN 记录。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl certificate approve csr-n9pvr
certificatesigningrequest.certificates.k8s.io/csr-n9pvr approved
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;通过之后，Kubelet 就有了使用 API Server 的 CA 签发的证书了。&lt;/p&gt;

&lt;p&gt;稍等片刻，再次执行 &lt;code&gt;kubectl top nodes&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl top nodes
NAME              CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
node-standard-1   213m         10%    1220Mi          70%
node-standard-2   71m          3%     361Mi           20%
node-standard-3   61m          3%     355Mi           20%
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Calico 3.5：根据节点标签分配 IP 地址</title>
      <link>/post/calico-assigning-ip/</link>
      <pubDate>Fri, 08 Feb 2019 11:15:50 +0800</pubDate>
      <guid>/post/calico-assigning-ip/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://docs.projectcalico.org/v3.5/usage/assigning-ip-addresses-topology&#34; target=&#34;_blank&#34;&gt;Assigning IP addresses based on topology&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;关于-ip-地址的分配&#34;&gt;关于 IP 地址的分配&lt;/h2&gt;

&lt;p&gt;Calico 能够进行配置，为不同拓扑指定 IP 地址池。例如可能希望某些机架、地区、或者区域能够从同一个 IP 池中获取地址。这对于降低路由数量或者配合防火墙策略的要求会很有帮助。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://docs.projectcalico.org/v3.5/reference/cni-plugin/configuration#ipam&#34; target=&#34;_blank&#34;&gt;cni 插件配置参考中的 IP 地址管理章节&lt;/a&gt;中包含了三种分配 IP 地址的方式。Kubernetes 注解方式只能用于 Namespace 或者 Pod 一级。剩下的只有两个办法，CNI 配置或者是基于节点选择器的 IP 池，相对于 CNI 配置的方式来说，节点选择器方案省去了修改本地文件的麻烦。&lt;/p&gt;

&lt;p&gt;在更高层次上，基于节点选择器的 IP 地址分配方法就是给节点设置标签，然后用节点选择器选择对应的 IP 地址池进行分配。后面的内容中将给出一个详细的例子，用这种方式来设置一种机架亲和方式的 IP 地址分配方案。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;如果 Calico 无法根据上述顺序来决定一个 IP 地址池，或者在选定的地址池中找不到可用的 IP 地址，那么这一工作负载就不会分到 IP 地址，无法启动。为了防止这种情况的发生，我们建议所有节点至少有一个合适的地址池。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;先决条件&#34;&gt;先决条件&lt;/h2&gt;

&lt;p&gt;这一功能需要 Calico 在 ETCD 模式下工作。&lt;/p&gt;

&lt;h2 id=&#34;示例-kubernetes&#34;&gt;示例：Kubernetes&lt;/h2&gt;

&lt;p&gt;本例中，我们会创建一个集群，其中包含四个节点，分布在两个机架上，每个机架各两台。示意如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;       -------------------
       |    router       |
       -------------------
       |                 |
---------------   ---------------
| rack-0      |   | rack-1      |
---------------   ---------------
| kube-node-0 |   | kube-node-2 |
- - - - - - - -   - - - - - - - -
| kube-node-1 |   | kube-node-3 |
- - - - - - - -   - - - - - - - -
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod IP 地址范围为 &lt;code&gt;192.168.0.0/16&lt;/code&gt;，我们进行如下设计：保留 &lt;code&gt;192.168.0.0/24&lt;/code&gt; 给 &lt;code&gt;rack-0&lt;/code&gt;, &lt;code&gt;192.168.1.0/24&lt;/code&gt; 给 &lt;code&gt;rack-1&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;要设置一个没有缺省地址池的的 Calico，首先运行 &lt;code&gt;calicoctl get ippool -o wide&lt;/code&gt;，会看到已经创建了一个 &lt;code&gt;192.168.0.0/16&lt;/code&gt; 的地址池：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;NAME                  CIDR             NAT    IPIPMODE   DISABLED   SELECTOR
default-ipv4-ippool   192.168.0.0/16   true   Always     false      all()
&lt;/code&gt;&lt;/pre&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;删除缺省地址池&lt;/p&gt;

&lt;p&gt;&lt;code&gt;default-ipv4-ippool&lt;/code&gt; 地址池已经存在，并占据了整个 &lt;code&gt;/16&lt;/code&gt; 块，因此必须删除：&lt;code&gt;calicoctl delete ippools default-ipv4-ippool&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;给 Node 打标签。&lt;/p&gt;

&lt;p&gt;要给特定节点分配地址池，节点必须用标签进行标识：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl label nodes kube-node-0 rack=0
kubectl label nodes kube-node-1 rack=0
kubectl label nodes kube-node-2 rack=1
kubectl label nodes kube-node-3 rack=1
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;为每个机架创建地址池&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;calicoctl create -f -&amp;lt;&amp;lt;EOF
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: rack-0-ippool
spec:
  cidr: 192.168.0.0/24
  ipipMode: Always
  natOutgoing: true
  nodeSelector: rack == &amp;quot;0&amp;quot;
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;calicoctl create -f -&amp;lt;&amp;lt;EOF
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: rack-1-ippool
spec:
  cidr: 192.168.1.0/24
  ipipMode: Always
  natOutgoing: true
  nodeSelector: rack == &amp;quot;1&amp;quot;
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在就创建了两个地址池，使用 &lt;code&gt;calicoctl get ippool -o wide&lt;/code&gt; 进行查看：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;NAME                  CIDR             NAT    IPIPMODE   DISABLED   SELECTOR
rack-1-ippool         192.168.0.0/24   true   Always     false      rack == &amp;quot;0&amp;quot;
rack-2-ippool         192.168.1.0/24   true   Always     false      rack == &amp;quot;1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;检查地址池的工作状态&lt;/p&gt;

&lt;p&gt;创建一个 Nginx 的 Deployment，其中包含五个副本，保证分配到每一个节点上。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl run nginx --image nginx --replicas 5
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查新的 Pod 是否已经根据所在机架获得了应有的 IP 地址。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;NAME                   READY   STATUS    RESTARTS   AGE    IP             NODE          NOMINATED NODE   READINESS GATES
nginx-5c7588df-prx4z   1/1     Running   0          6m3s   192.168.0.64   kube-node-0   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx-5c7588df-s7qw6   1/1     Running   0          6m7s   192.168.0.129  kube-node-1   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx-5c7588df-w7r7g   1/1     Running   0          6m3s   192.168.1.65   kube-node-2   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx-5c7588df-62lnf   1/1     Running   0          6m3s   192.168.1.1    kube-node-3   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx-5c7588df-pnsvv   1/1     Running   0          6m3s   192.168.1.64   kube-node-2   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，IP 地址的是根据节点（所在的机架）来选择了对应的地址池进行分配的。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>开发和 Kubernetes 之间的鸿沟</title>
      <link>/post/gap-between-dev-and-k8s/</link>
      <pubDate>Tue, 05 Feb 2019 22:01:13 +0800</pubDate>
      <guid>/post/gap-between-dev-and-k8s/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://medium.com/@kent.rancourt/theres-a-gap-between-devs-and-kubernetes-f67f06c73454&#34; target=&#34;_blank&#34;&gt;There’s a Gap Between Devs and Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://medium.com/@kent.rancourt/&#34; target=&#34;_blank&#34;&gt;Kent Rancourt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我用 Kubernetes 谋生，多数时间里，我都在为 Kubernetes 开发开源软件平台、中间件以及工具。我经常会问我自己以及我的同事：“我们要解决的问题是什么？”以及“我们要为谁解决问题？”。这种提问让我们能够保持对目标的专注，明确产品的价值。我也会把类似的问题抛给前同事们以及本地 Meetup 的参与者——他们各自的公司都在 “Kubernetes 之旅”的途中。如果他们正在构建或准备采用的是一个新的 Kubernetes 的相关工具，我通常会询问要解决的问题以及为谁解决问题。更多情况下，我会询问他们在采用 Kubernetes 的过程中遇到的问题。&lt;/p&gt;

&lt;p&gt;不管采用 Kubernetes 的进度如何，两个回应是最常见的。第一个是“Kubernetes 太难了”，另一个是“Kubernetes 的抽象是错的”。第二个问题经常会用设问的方式提出：“Kubernetes 的抽象对么？”。&lt;/p&gt;

&lt;p&gt;这种陈述缺乏了重要的上下文。其中没有澄清 Kubernetes 对&lt;strong&gt;谁&lt;/strong&gt;来说太难了？对&lt;strong&gt;谁&lt;/strong&gt;来说抽象有问题？在进一步的探求中，我得到的答案基本上来说是一致的：“开发者”。那么前面的陈述就可以修正为：Kubernetes 对开发者来说太难了，对开发者来说，Kubernetes 的抽象是不对的。&lt;/p&gt;

&lt;p&gt;“对开发者来说，Kubernetes 的抽象是错误的”，这个陈述通常的意思就是，Kubernetes 只提供了用于声明应用部署和服务的原语，而开发者更多关注的是商业价值的交付过程，而非 Kubernetes 细枝末节的学习，Kubernetes 没有提供更符合开发者期待的抽象模型。它没有提供部署应用的直接选项。两个问题合二为一——对于开发者来说，Kubernetes 的抽象是错误的，因此就太难了。&lt;/p&gt;

&lt;p&gt;对此，我的观点是，Kubernetes 不是为开发者而生的。很多人会同意我的观点，但是令人惊奇的是，很多人压根没想过这个问题。&lt;/p&gt;

&lt;p&gt;更少就是更多？对开发者来说，正确的抽象就是完全不抽象？毕竟在 Kubernetes 甚至是 Docker 诞生之前的几十年里，开发者一直使用自己惯用的技术栈进行开发和测试，所谓的应用抽象（或者 Kubernetes）的缺失，并不会让这些工作无法进行。我对开发者的建议是，开发者可以不关注 Kubernetes。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-是谁的&#34;&gt;Kubernetes 是谁的？&lt;/h2&gt;

&lt;p&gt;在 Kubernetes 之前，组织中会存在一个角色，可以称其为部署专家，这个角色的职责是构建并部署应用和服务。他们需要对平台或相应的技术堆栈有着相当详细的认识，还可能需要一些复杂的、深奥的知识背景，有些自制的部署工具，以及大量的脚本。其工作可能是：部署 Ruby 应用、Python 应用、Tomcat 上的 Java 应用、WebSphere 应用等等不同的任务。这些人才是 Kubernetes 的目标用户——他们熟知应用部署的知识。Kubernetes 为这些用户提提供了能描述所有（容器化）应用的一致的部署模型，能够很好地提高其生产力并降低其学习成本。&lt;/p&gt;

&lt;p&gt;经常有一个对 Kubernetes 的误解就是平台（“平台即服务”中的平台），其实它不是。即使是清楚这一点的人也往往对此视而不见，这样就会忽视掉部署专家这一角色的存在。潜意识中，对开发人员产生了一种不切实际的期望：他们应该能够自己使用 Kubernetes，从而可以取代部署专家这一角色。&lt;/p&gt;

&lt;p&gt;知道了这些，也就成功了一半了。Kubernetes 并非为开发人员而生，更进一步思考，有三个可能的办法。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;我认为最好将开发专家的角色重新加入组织。要完成这一过程，部署专家必须融入到开发团队中，或至少与开发团队紧密合作——和过去一样。我倾向于这种方法的原因是一个称职的部署专家能够优化部署并完成复杂的场景，其他方法难于做到这一点。不幸的是，这种办法可能最难落地。这种方式要对组织进行改进而非技术，从而弥合开发者和 Kubernetes 之间的鸿沟，但是大多数组织缺乏这样的自觉和进取心。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;可能你是一个初创企业，只有五个开发者，没人想再承担一个部署专家的角色；又可能你是一个有几千开发者的企业，无法将部署专家的角色扩展到支持整个开发团队，资源限制之下，第一个办法无法实施。这种情况下，就只能靠工具或者平台之类的技术手段来拉近开发者和 Kubernetes 了。坦率的说，这就是我和很多人的饭碗。然而不要低估识别和构建合适方案的成本（自建平台正在大行其道），其所需成本可能轻松地超越第一种方法。另外也不应忽视的一点是，平台和工具也都有自己的侧重点，可能会让用户陷入新的困境。用两个新问题解决一个旧问题可能不是个好办法。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;最后一点，可能你的组织还没有准备好采用 Kubernetes——甚至可能并不需要它。如果一个 Heroku 这样的 PaaS 甚至一个非 Kubernetes 的自建平台能够满足组织的需求，为什么还要给 Kubernetes 缴税？这种情况下，消灭开发人员和 Kubernetes 之间鸿沟的最好方法就是——不要使用 Kubernetes。再次提醒：准备好受到限制，并清楚认识未来走向。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Kubernetes 并非为开发者而生，不要太快的认为，技术是唯一填补这一鸿沟的方法。强烈建议考虑，首先在组织层面做出改变。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Google 宣布 Kubernetes Operator for Spark</title>
      <link>/post/google-announces-k8s-operator-for-spark/</link>
      <pubDate>Fri, 01 Feb 2019 18:29:25 +0800</pubDate>
      <guid>/post/google-announces-k8s-operator-for-spark/</guid>
      <description>

&lt;p&gt;作者：&lt;a href=&#34;https://www.zdnet.com/meet-the-team/us/andrew-brust/#ftag=CAD-00-10aag7e&#34; target=&#34;_blank&#34;&gt;Andrew Brust&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文：&lt;a href=&#34;https://www.zdnet.com/google-amp/article/google-announces-kubernetes-operator-for-apache-spark&#34; target=&#34;_blank&#34;&gt;Google announces Kubernetes Operator for Apache Spark&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Spark Operator” 的 Beta 版本，可以用来在 Kubernetes 上执行原生 Spark 应用，无需 Hadoop 或 Mesos。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&#34;https://spark.apache.org/&#34; target=&#34;_blank&#34;&gt;Apache Spark&lt;/a&gt;是一个流行的执行框架，用于执行数据工程和机器学习方面的工作负载。他提供 &lt;a href=&#34;http://www.databricks.com/&#34; target=&#34;_blank&#34;&gt;Databricks&lt;/a&gt; 平台的支持，可用于内部部署的或者公有云的 Hadoop 服务，例如 &lt;a href=&#34;https://azure.microsoft.com/services/hdinsight/&#34; target=&#34;_blank&#34;&gt;Azure HDInsight&lt;/a&gt;、&lt;a href=&#34;https://aws.amazon.com/emr/&#34; target=&#34;_blank&#34;&gt;Amazon EMR&lt;/a&gt;、以及 &lt;a href=&#34;https://cloud.google.com/dataproc/&#34; target=&#34;_blank&#34;&gt;Google Cloud Dataproc&lt;/a&gt;，也可以在 &lt;a href=&#34;http://mesos.apache.org/&#34; target=&#34;_blank&#34;&gt;Mesos&lt;/a&gt; 集群上运行。&lt;/p&gt;

&lt;p&gt;但是如果只是想在 &lt;a href=&#34;https://kubernetes.io/&#34; target=&#34;_blank&#34;&gt;Kubernetes(k8s)&lt;/a&gt; 而非 Mesos 上运行 Spark 工作负载，也不想使用 &lt;a href=&#34;https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/YARN.html&#34; target=&#34;_blank&#34;&gt;YARN&lt;/a&gt;，这可行么？Spark 在 2.3 版本中首次加入了针对 Kubernetes 的功能，并在 2.4 中进行了进一步增强，然而让 Spark 用全集成的方式原生运行在 Kubernetes 上，仍然是非常有挑战的。&lt;/p&gt;

&lt;h2 id=&#34;kube-operator&#34;&gt;Kube Operator&lt;/h2&gt;

&lt;p&gt;Kubernetes 的始作俑者 Google，宣布了 &lt;a href=&#34;https://github.com/GoogleCloudPlatform/spark-on-k8s-operator&#34; target=&#34;_blank&#34;&gt;Kubernetes Operator for Apache Spark&lt;/a&gt; 的 Beta 版本，简称 &lt;code&gt;Spark Operator&lt;/code&gt;。Spark Operator 让 Spark 可以原生运行在 Kubernetes 集群上。 Spark 应用（这些应用用于分析、数据工程或者机器学习）可以部署在这些集群上运行，像在其它集群上一样。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/spark-operator-architectecure-diagram.png&#34; alt=&#34;Spark Operator Architectecure&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Google 声明，Spark Operator 是一个 &lt;a href=&#34;https://kubernetes.io/docs/concepts/api-extension/custom-resources/#custom-controllers&#34; target=&#34;_blank&#34;&gt;Kubernetes 自定义控制器&lt;/a&gt;，其中使用&lt;a href=&#34;https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/master/docs/api.md&#34; target=&#34;_blank&#34;&gt;自定义资源&lt;/a&gt;来声明 Spark 应用的元数据；它还支持自动重启动以及基于 &lt;a href=&#34;https://en.wikipedia.org/wiki/Cron&#34; target=&#34;_blank&#34;&gt;cron&lt;/a&gt; 的计划任务。今后，开发者、数据工程师以及数据科学家可以创建声明式的规范，来描述他们的 Spark 应用，并使用原生的 Kubernetes 工具（例如 Kubectl）来管理他们的应用。&lt;/p&gt;

&lt;h2 id=&#34;现在就试试&#34;&gt;现在就试试&lt;/h2&gt;

&lt;p&gt;Spark Operator 目前在 &lt;a href=&#34;https://console.cloud.google.com/marketplace/details/google/spark-operator&#34; target=&#34;_blank&#34;&gt;GCP 的 Kubernetes 市场&lt;/a&gt;中已经可用，可以方便的部署到 &lt;a href=&#34;https://cloud.google.com/kubernetes-engine/&#34; target=&#34;_blank&#34;&gt;Google Kubernetes Engine(GKE)&lt;/a&gt;。另外 Spark Operator 是一个开源项目，能够部署在任何 Kubernetes 环境中，项目的 Github 页面提供了基于 Helm Chart 的&lt;a href=&#34;https://github.com/helm/charts/tree/master/incubator/sparkoperator&#34; target=&#34;_blank&#34;&gt;安装指南&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;如果 Amazon 和微软这样的厂商任何并在自家的 Kubernetes 服务上（微软的 AKS 以及 Amazon 的 ECS）提供 Spark Operator 的部署方式，会是个有意思的局面。这对他们的客户来说会是一个很棒的服务，客户并不想要在 EMR、HDInsight 或者 Daabricks 的工作空间和集群上付出开销。&lt;/p&gt;

&lt;h2 id=&#34;hadoop-怎么办&#34;&gt;Hadoop 怎么办&lt;/h2&gt;

&lt;p&gt;很多非 Databricks 的 Spark 集群是运行在 Hadoop 上的。Spark Operators 的出现，是否意味着 Hadoop 的影响被削弱了？Hadoop 团队也并非游手好闲之辈，例如 &lt;a href=&#34;https://hortonworks.com/blog/bringing-cloud-native-architecture-to-big-data-in-the-data-center/&#34; target=&#34;_blank&#34;&gt;开放混合架构草案&lt;/a&gt;就聚焦于 Hadoop 的容器化。另外上周发布的 &lt;a href=&#34;https://blogs.apache.org/foundation/entry/the-apache-software-foundation-announces45&#34; target=&#34;_blank&#34;&gt;Hadoop 3.2&lt;/a&gt;，其功能就包括了对 &lt;a href=&#34;https://www.tensorflow.org/&#34; target=&#34;_blank&#34;&gt;Tensorflow&lt;/a&gt; 的支持，&lt;a href=&#34;https://azure.microsoft.com/en-us/services/storage/data-lake-storage/&#34; target=&#34;_blank&#34;&gt;Azure Data Lake Storage Gen2&lt;/a&gt; 的链接支持以及增强的&lt;a href=&#34;https://aws.amazon.com/s3/&#34; target=&#34;_blank&#34;&gt;Amazon S3&lt;/a&gt; 的增强支持。&lt;/p&gt;

&lt;p&gt;消费者和往常一样，将在激烈的竞争中获益。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 Knative 中进行应用程序的构建和部署</title>
      <link>/post/building-and-deploying-applications-to-knative/</link>
      <pubDate>Tue, 13 Nov 2018 00:10:38 +0800</pubDate>
      <guid>/post/building-and-deploying-applications-to-knative/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://starkandwayne.com/blog/building-and-deploying-applications-to-knative/&#34; target=&#34;_blank&#34;&gt;Building and deploying applications to Knative&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://twitter.com/starkandwayne&#34; target=&#34;_blank&#34;&gt;Dr Nic Williams&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Knative 有三个高级子系统：Serving 用来协调服务 Pod 的自动伸缩以及路由；Build 提供了将代码转换为镜像的工具链；Eventing 则会使用事件的发布订阅来触发松耦合服务。&lt;/p&gt;

&lt;p&gt;前一篇文章中我们将一个构件好的容器镜像发布到了 Knative Serving 中。&lt;/p&gt;

&lt;p&gt;本篇文章将使用 Knative Build 把我们的应用通过 Dockerfile 以及 Cloud Foundry buildpack 在发布过程中转化为容器镜像。我们还会尝试从本地文件系统以及远程 Git 仓库中进行部署的方式。&lt;/p&gt;

&lt;p&gt;前面的操作都很简单，看得出在 Kubernetes 环境中安装 Knative、运行现有容器镜像、使用 curl 与应用进行交互都不难。总结一下就是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;knctl install [--node-ports] [--exclude-monitoring]
knctl deploy --service &amp;lt;service-name&amp;gt; --image &amp;lt;image-name&amp;gt;
knctl curl --service &amp;lt;service-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;后面的两步 - 把镜像部署为 Kubernetes 并运行，为每个部署进行版本化处理，使用 HTTP 路由进行交互，都是 &lt;a href=&#34;https://github.com/knative/serving/blob/master/docs/spec/overview.md&#34; target=&#34;_blank&#34;&gt;Serving 子系统&lt;/a&gt;的功劳。&lt;/p&gt;

&lt;p&gt;Knative 还支持用于构建容器镜像的弹性子系统，构建生成的镜像将会以 Kubernetes Pod 的形式运行。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/knative/docs/tree/master/build&#34; target=&#34;_blank&#34;&gt;Knative Build&lt;/a&gt; 子系统非常有弹性。我们会探讨集中用例：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;上传一个本地目录，使用 Dockerfile 进行构建。&lt;/li&gt;
&lt;li&gt;上传一个本地目录，使用 Buildpack 进行构建。&lt;/li&gt;
&lt;li&gt;用上面两种方式来构建来自于远程 Git 仓库的代码。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;命名空间&#34;&gt;命名空间&lt;/h2&gt;

&lt;p&gt;前一篇文章中我们在每个 &lt;code&gt;knctl&lt;/code&gt; 命令中使用了 &lt;code&gt;--namespace helloworld&lt;/code&gt; 参数来显式的指定命名空间。对我来说这样显得更清晰。可能有别的用户希望设置一个缺省命名空间，从而能够缩短命令。&lt;/p&gt;

&lt;p&gt;可以给 &lt;code&gt;knctl&lt;/code&gt; 配置一个当前命名空间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl create ns my-simple-app
export KNCTL_NAMESPACE=my-simple-app
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所有的 &lt;code&gt;knctl&lt;/code&gt; 命令都会使用这一命名空间。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;$ knctl service list
Services in namespace &#39;my-simple-app&#39;

Name  Domain  Annotations  Age

0 services
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以在 &lt;code&gt;kubectl&lt;/code&gt; 命令中复用 &lt;code&gt;$KNCTL_NAMESPACE&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get pods -n $KNCTL_NAMESPACE&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&#34;上传一个带有-dockerfile-的本地目录&#34;&gt;上传一个带有 Dockerfile 的本地目录&lt;/h2&gt;

&lt;p&gt;在所有 Knative Build 的示例中，都会产生一个副产品——容器镜像。这些镜像必须放在什么地方，例如 &lt;a href=&#34;https://hub.docker.com/&#34; target=&#34;_blank&#34;&gt;Docker Hub&lt;/a&gt;、&lt;a href=&#34;https://cloud.google.com/container-registry/&#34; target=&#34;_blank&#34;&gt;GCP 容器库&lt;/a&gt;、&lt;a href=&#34;https://azure.microsoft.com/en-au/services/container-registry/&#34; target=&#34;_blank&#34;&gt;Azure 容器库&lt;/a&gt;，或者借助 &lt;a href=&#34;https://goharbor.io/&#34; target=&#34;_blank&#34;&gt;Harbor&lt;/a&gt; 之类的软件自建的私库。&lt;/p&gt;

&lt;p&gt;我们需要在每个应用所在的 Kubernetes 命名空间中给 Knative 配置一个镜像库，然后 &lt;code&gt;knctl basic-auth-secret create&lt;/code&gt; 给 Knative 配置一个 Secret。&lt;/p&gt;

&lt;p&gt;以 Docker Hub 为例，使用 &lt;code&gt;--docker-hub&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;knctl basic-auth-secret create -s registry --docker-hub -u &amp;lt;username&amp;gt; -p &amp;lt;password&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;GCP 容器仓库可以使用 &lt;code&gt;--gcr&lt;/code&gt; 选项：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;knctl basic-auth-secret create -s registry --gcr -u &amp;lt;username&amp;gt; -p &amp;lt;password&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其它私库可以使用 &lt;code&gt;--type&lt;/code&gt; 和 &lt;code&gt;--url&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;knctl basic-auth-secret create -s registry --type docker --url https://registry.domain.com/ -u &amp;lt;username&amp;gt; -p &amp;lt;password&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下一步把镜像库 Secret 映射到 &lt;a href=&#34;https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/&#34; target=&#34;_blank&#34;&gt;Kubernetes Service account&lt;/a&gt;，它会在 Knative Build 的 Pod 中提供上面的登录信息。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;knctl service-account create --service-account build -s registry
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个操作会体现在 Kubernetes 的 Service account 中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get serviceaccount -n $KNCTL_NAMESPACE
NAME      SECRETS   AGE
build     2         37s
default   1         3h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样我们就准备好使用 Knative Build 创建新容器镜像所需的认证凭据了。&lt;/p&gt;

&lt;p&gt;Clone 一个 Go 应用作为样例，并从它的本地目录推送到 Docker hub：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;git clone https://github.com/cppforlife/simple-app
cd simple-app

DOCKER_IMAGE=index.docker.io/&amp;lt;your hub.docker.com org or user&amp;gt;/knative-simple-app

knctl deploy \
    --service simple-app \
    --directory=$PWD \
    --service-account build \
    --image ${DOCKER_IMAGE:?required} \
    --env SIMPLE_MSG=&amp;quot;Built from local directory using Dockerfile&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;容器的显式命名事实上只是从 Build 到 Serve 的工作过程中的一个中间步骤的副产品，但是必须提供。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;knctl deploy&lt;/code&gt; 的输出大概是这样的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;Name  simple-app

Waiting for new revision to be created...

Tagging new revision &#39;simple-app-00001&#39; as &#39;latest&#39;

Tagging new revision &#39;simple-app-00001&#39; as &#39;previous&#39;

[2018-10-15T13:18:31+10:00] Uploading source code...

[2018-10-15T13:19:59+10:00] Finished uploading source code...

Watching build logs...

build-step-build-and-push | INFO[0000] Downloading base image golang:1.10.1
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:20:01.547607       1 metadata.go:142] while reading &#39;google-dockercfg&#39; metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:20:01.550268       1 metadata.go:159] while reading &#39;google-dockercfg-url&#39; metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg-url
build-step-build-and-push | INFO[0001] Executing 0 build triggers
build-step-build-and-push | INFO[0001] Extracting layer 0
build-step-build-and-push | INFO[0003] Extracting layer 1
build-step-build-and-push | INFO[0004] Extracting layer 2
build-step-build-and-push | INFO[0004] Extracting layer 3
build-step-build-and-push | INFO[0007] Extracting layer 4
build-step-build-and-push | INFO[0010] Extracting layer 5
build-step-build-and-push | INFO[0015] Extracting layer 6
build-step-build-and-push | INFO[0015] Taking snapshot of full filesystem...
build-step-build-and-push | INFO[0027] WORKDIR /go/src/github.com/mchmarny/simple-app/
build-step-build-and-push | INFO[0027] cmd: workdir
build-step-build-and-push | INFO[0027] Changed working directory to /go/src/github.com/mchmarny/simple-app/
build-step-build-and-push | INFO[0027] Creating directory /go/src/github.com/mchmarny/simple-app/
build-step-build-and-push | INFO[0027] COPY . .
build-step-build-and-push | INFO[0027] RUN CGO_ENABLED=0 GOOS=linux go build -v -o app
build-step-build-and-push | INFO[0027] cmd: /bin/sh
build-step-build-and-push | INFO[0027] args: [-c CGO_ENABLED=0 GOOS=linux go build -v -o app]
build-step-build-and-push | net
build-step-build-and-push | vendor/golang_org/x/net/lex/httplex
build-step-build-and-push | vendor/golang_org/x/net/proxy
build-step-build-and-push | net/textproto
build-step-build-and-push | crypto/x509
build-step-build-and-push | crypto/tls
build-step-build-and-push | net/http/httptrace
build-step-build-and-push | net/http
build-step-build-and-push | github.com/mchmarny/simple-app
build-step-build-and-push | INFO[0030] Taking snapshot of full filesystem...
build-step-build-and-push | INFO[0034] Storing source image from stage 0 at path /kaniko/stages/0
build-step-build-and-push | INFO[0038] trying to extract to /kaniko/0
build-step-build-and-push | INFO[0038] Extracting layer 0
build-step-build-and-push | INFO[0040] Extracting layer 1
build-step-build-and-push | INFO[0041] Extracting layer 2
build-step-build-and-push | INFO[0041] Extracting layer 3
build-step-build-and-push | INFO[0043] Extracting layer 4
build-step-build-and-push | INFO[0046] Extracting layer 5
build-step-build-and-push | INFO[0051] Extracting layer 6
build-step-build-and-push | INFO[0051] Extracting layer 7
build-step-build-and-push | INFO[0051] Deleting filesystem...
build-step-build-and-push | INFO[0053] No base image, nothing to extract
build-step-build-and-push | INFO[0053] Taking snapshot of full filesystem...
build-step-build-and-push | INFO[0062] COPY --from=0 /go/src/github.com/mchmarny/simple-app/app .
build-step-build-and-push | INFO[0063] Taking snapshot of files...
build-step-build-and-push | INFO[0063] EXPOSE 8080
build-step-build-and-push | INFO[0063] cmd: EXPOSE
build-step-build-and-push | INFO[0063] Adding exposed port: 8080/tcp
build-step-build-and-push | INFO[0063] ENTRYPOINT [&amp;quot;/app&amp;quot;]
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:21:04.751338       1 metadata.go:142] while reading &#39;google-dockercfg&#39; metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:21:04.753927       1 metadata.go:159] while reading &#39;google-dockercfg-url&#39; metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg-url
build-step-build-and-push | 2018/10/15 03:21:06 pushed blob sha256:72a682eea3309941d5e8e6f993a07ae4d33a413b8b7fa2762f8e969310b5996a
build-step-build-and-push | 2018/10/15 03:21:07 pushed blob sha256:9c24aa788ba416c5e1e631d8af3e3115519ad7ca0f659ac10f40682524c6d9cd
build-step-build-and-push | 2018/10/15 03:21:07 index.docker.io/drnic/knative-simple-app:latest: digest: sha256:b5823ead77d9544998b5bc844f049d1a7dfb0aefe7461b74b3e4f67fb5481fa1 size: 428
nop | Nothing to push

Succeeded
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;knative-build-的调试&#34;&gt;Knative Build 的调试&lt;/h2&gt;

&lt;p&gt;目前 &lt;code&gt;knctl deploy&lt;/code&gt; 没有显示任何来自 Knative Build 系统的内部错误或者警告。只需要看着 &lt;code&gt;Waiting for new revision to be created...&lt;/code&gt; 坐享其成就可以了。&lt;/p&gt;

&lt;p&gt;一个调试方法就是使用 &lt;a href=&#34;https://github.com/boz/kail&#34; target=&#34;_blank&#34;&gt;kail&lt;/a&gt; 工具处理来自 Knative Build 子系统的消息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kail -n knative-build
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样就会看到大量的日志，可以再其中查找错误信息，例如 &lt;code&gt;&amp;quot;msg&amp;quot;:&amp;quot;Failed the resource specific validation{error 25 0 serviceaccounts \&amp;quot;build\&amp;quot; not found}&amp;quot;&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;使用-buildpack-进行构建&#34;&gt;使用 Buildpack 进行构建&lt;/h2&gt;

&lt;p&gt;我本人很喜欢 Cloud Foundry 和 Heroku 的镜像构建方式，幸运的是，Knative Build 通过&lt;a href=&#34;https://github.com/knative/build-templates/tree/master/buildpack&#34; target=&#34;_blank&#34;&gt;自定义构建模板&lt;/a&gt;的方式提供了这种支持。&lt;/p&gt;

&lt;p&gt;首先用 &lt;code&gt;buildpack&lt;/code&gt; 这个名字在活动命名空间中注册一个&lt;a href=&#34;https://github.com/knative/build-templates/blob/master/buildpack/buildpack.yaml&#34; target=&#34;_blank&#34;&gt;构建模板&lt;/a&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl -n $KNCTL_NAMESPACE apply -f \
https://raw.githubusercontent.com/knative/build-templates/master/buildpack/buildpack.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;加入 &lt;code&gt;--template buildpack&lt;/code&gt; 就可以使用这一自定义模板了。构建模板所需的附加环境变量都可以用 &lt;code&gt;--template-env NAME=value&lt;/code&gt; 的方式进行植入。&lt;/p&gt;

&lt;p&gt;例如 Cloud Foundry Go Buildpack 需要 &lt;code&gt;$GOPACKNAME&lt;/code&gt;（&lt;a href=&#34;https://docs.cloudfoundry.org/buildpacks/go/index.html#pushing_Apps_with_native_Go_vendoring&#34; target=&#34;_blank&#34;&gt;参考文档&lt;/a&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;knctl deploy \
    --service simple-app \
    --directory=$PWD \
    --service-account build \
    --image ${DOCKER_IMAGE:?required} \
    --env SIMPLE_MSG=&amp;quot;Built from local directory using Buildpack template&amp;quot; \
    --template buildpack \
    --template-env GOPACKAGENAME=main
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;输出内容和 Cloud Foundry buildpack 是一致的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;Name  simple-app

Waiting for new revision (after revision &#39;simple-app-00001&#39;) to be created...

Tagging new revision &#39;simple-app-00002&#39; as &#39;latest&#39;

Tagging older revision &#39;simple-app-00001&#39; as &#39;previous&#39;

[2018-10-15T13:40:41+10:00] Uploading source code...

[2018-10-15T13:42:08+10:00] Finished uploading source code...

Watching build logs...

build-step-build | -----&amp;gt; Go Buildpack version 1.8.26
build-step-build | -----&amp;gt; Installing godep 80
build-step-build |        Download [https://buildpacks.cloudfoundry.org/dependencies/godep/godep-v80-linux-x64-cflinuxfs2-06cdb761.tgz]
build-step-build | -----&amp;gt; Installing glide 0.13.1
build-step-build |        Download [https://buildpacks.cloudfoundry.org/dependencies/glide/glide-v0.13.1-linux-x64-cflinuxfs2-aab48c6b.tgz]
build-step-build | -----&amp;gt; Installing dep 0.5.0
build-step-build |        Download [https://buildpacks.cloudfoundry.org/dependencies/dep/dep-v0.5.0-linux-x64-cflinuxfs2-52c14116.tgz]
build-step-build | -----&amp;gt; Installing go 1.8.7
build-step-build |        Download [https://buildpacks.cloudfoundry.org/dependencies/go/go1.8.7.linux-amd64-cflinuxfs2-fff10274.tar.gz]
build-step-build |        **WARNING** Installing package &#39;.&#39; (default)
build-step-build | -----&amp;gt; Running: go install -tags cloudfoundry -buildmode pie .
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:1124eb40dd68654b8ca8f5d9ec7e439988a4be752a58c8f4e06d60ab1589abdb
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:6be38da025345ffb57d1ddfcdc5a2bc052be5b9491825f648b49913d51e41acb
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:a5733e6358eec8957e81b1eb93d48ef94d649d65c69a6b1ac49f616a34a74ac1
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:21324a9f04e76c93078f3a782e3198d2dded46e4ec77958ddd64f701aecb69c0
build-step-export | 2018/10/15 03:47:59 pushed blob sha256:efa2d34b82bc07588a1a8fd4526322257408109547ee089a792b3f51c383f8e6
build-step-export | 2018/10/15 03:47:59 pushed blob sha256:d495696b33936c79216ec8178726b9fbe915fafbffdd0911a7fdabce4297d9a4
build-step-export | 2018/10/15 03:48:00 index.docker.io/drnic/knative-simple-app:latest: digest: sha256:e5ef1d4d255b4bcbb38d4b43bb6302423c33e6eeabd0e20d5fda4e5ce4c46668 size: 1082
nop | Nothing to push
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在就能看到应用已经部署成功：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ knctl curl -s simple-app
&amp;lt;h1&amp;gt;Built from local directory using Buildpack template&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;私有-git-secret&#34;&gt;私有 Git Secret&lt;/h2&gt;

&lt;p&gt;前面两节我们从本地上传了源码然后构建了 Docker 镜像（使用 Dockerfile 或 Cloud Foundry buildpack），最后运行应用。&lt;/p&gt;

&lt;p&gt;Knative 还能从 Git 仓库获取源码（正式的说法是，Knative Build 只支持从 Git 仓库获取源码，本地代码的支持是 &lt;code&gt;knctl&lt;/code&gt; 提供的）。&lt;/p&gt;

&lt;p&gt;让 Knative Build 获取 Git 仓库中的代码，需要用 &lt;code&gt;--git-url&lt;/code&gt; 和 &lt;code&gt;--git-revision&lt;/code&gt; 来取代 &lt;code&gt;--directory=$PWD&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;如果你的 Git 仓库是私有的，那就还需要在 Service account（在上面的例子中的 &lt;code&gt;build&lt;/code&gt;）里包含 Git ssh 凭据。&lt;code&gt;knctl ssh-auth-secret create&lt;/code&gt; 能够协助用户创建一个 kubernetes.io/ssh-auth secret。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ knctl ssh-auth-secret create --secret git --github --private-key &amp;quot;$(cat ~/.ssh/id_rsa)&amp;quot;
Name  git
Type  kubernetes.io/ssh-auth

$ kubectl get secrets -n $KNCTL_NAMESPACE
NAME                  TYPE                                  DATA   AGE
...
git                   kubernetes.io/ssh-auth                1      5m
registry              kubernetes.io/basic-auth              2      3h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在需要把 &lt;code&gt;git&lt;/code&gt; secret 加入到我们的 &lt;code&gt;build&lt;/code&gt; Service account 之中了。&lt;/p&gt;

&lt;p&gt;在成文之时，&lt;code&gt;knctl&lt;/code&gt; 还没有提供 &lt;code&gt;knctl serviceaccounts update&lt;/code&gt; 这样的命令，所以需要删除重新创建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl delete serviceaccounts -n $KNCTL_NAMESPACE build
knctl service-account create --service-account build -s registry -s git
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;从-git-部署&#34;&gt;从 Git 部署&lt;/h2&gt;

&lt;p&gt;用 &lt;code&gt;--git-url&lt;/code&gt; 和 &lt;code&gt;--git-revision&lt;/code&gt; 替代 &lt;code&gt;--directory&lt;/code&gt; 来进行 Git 部署：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;knctl deploy \
    --service simple-app \
    --git-url git@github.com:cppforlife/simple-app.git \
    --git-revision master \
    --service-account build \
    --image ${DOCKER_IMAGE:?required} \
    --env SIMPLE_MSG=&amp;quot;Built from Git repo using Buildpack template&amp;quot; \
    --template buildpack \
    --template-env GOPACKAGENAME=main
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;&lt;code&gt;knctl deploy&lt;/code&gt; 命令在 Knative 的基础上提供了创建新镜像的良好体验，可以从本地目录或者 Git 仓库开始，使用 Dockerfile 或 Cloud Foundry buildpack 进行构建，并支持不同的镜像仓库。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 Knative 上部署 12 要素应用程序</title>
      <link>/post/deploying-12-factor-apps-to-knative/</link>
      <pubDate>Mon, 12 Nov 2018 00:35:24 +0800</pubDate>
      <guid>/post/deploying-12-factor-apps-to-knative/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://starkandwayne.com/blog/introduction-to-knative/&#34; target=&#34;_blank&#34;&gt;Deploying 12 Factor Apps to Knative&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://twitter.com/starkandwayne&#34; target=&#34;_blank&#34;&gt;Dr Nic Williams&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;我尝试按照本文内容在 Azure 上使用 ACS-Engine 部署的 Kubernetes 上进行了测试，版本部分的案例得到了相反结果，不过还是按照原文发了出来，作为一个获取感性认识的入门还是不错的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Google Next18 活动中，Google 宣称将会把 GKE 上的无服务器插件以 &lt;a href=&#34;https://github.com/knative/&#34; target=&#34;_blank&#34;&gt;Knative&lt;/a&gt; 的名称进行开源。当时它被描述为无服务器平台的构建块，由此推断，Knative 可能需要 Google、Pivotal 或者 RedHat 的协助才能使用。这可能是开源的古怪时机。从我最初的摸索来看，Knative 能工作；当我把 Heroku/Cloud Foundry buildpacks 加入进来之后，整个系统变得越来越像 Heroku/Cloud Foundry，相对于原始 Kubernetes，我更加了解和喜爱这一系统。&lt;/p&gt;

&lt;p&gt;本文中我们会将 Knative 安装到你自己的 Kubernetes 集群中（&lt;code&gt;knctl install&lt;/code&gt;），然后用 Knative 做些有趣的事情（&lt;code&gt;knctl deploy&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;Knative 能够为在 Kubernetes 集群上运行无状态应用的运维人员带来很多惊喜。对我来说，最引人入胜的一点就是伸缩性：在高负载时候进行扩容，没人喜欢你的应用了，就会一直缩容到 0。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/cppforlife/knctl/releases&#34; target=&#34;_blank&#34;&gt;下载和安装&lt;/a&gt; Knative 客户端工具 &lt;code&gt;knctl&lt;/code&gt;，然后就可以在你的 Kubernetes 上部署 Knative，然后发布你的应用了。&lt;/p&gt;

&lt;p&gt;在 MacOS 中，可以利用我们的 Homebrew tap 进行安装：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install starkandwayne/kubernetes/knctl&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这里我假设你再使用 Minikube。在 Minikube 中，可以使用 Node Port 代替 Load balancer：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;minikube start --memory=8192 --cpus=3 \
  --kubernetes-version=v1.11.3 \
  --vm-driver=hyperkit \
  --bootstrapper=kubeadm

knctl install --node-ports --exclude-monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以参考 &lt;a href=&#34;https://github.com/knative/docs/tree/master/install&#34; target=&#34;_blank&#34;&gt;Knative 文档&lt;/a&gt;来获取在其它类型 Kubernetes 集群上进行部署的要点。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;knctl install&lt;/code&gt; 命令可能要花上几分钟，甚至是十分钟或者更多。这个过程中需要下载大概一打镜像。不管是互联网带宽还是镜像尺寸都可能有变化，所以坐下放松一会，或者出去走走也好。&lt;/p&gt;

&lt;p&gt;如果 &lt;code&gt;knctl install&lt;/code&gt; 失败了，可能是你的 Internet 比较慢，Docker 镜像在命令超时之前还没能完成下载。运行 &lt;code&gt;kubectl get pods --all-namespaces&lt;/code&gt; 直到所有 Pod 都在运行，然后再次运行 &lt;code&gt;knctl install&lt;/code&gt; 命令继续完成安装过程。&lt;/p&gt;

&lt;p&gt;现在你的 Kubernetes 就是无服务器架构了，不错吧。&lt;/p&gt;

&lt;p&gt;可以运行 &lt;code&gt;kubectl get pods --all-namespaces&lt;/code&gt; 看看原始的 Knative 的 Pod 们。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;$ kubectl get pods --all-namespaces
NAMESPACE         NAME                                        READY   STATUS      RESTARTS   AGE
istio-system      istio-citadel-7d8f9748c5-zgm9x              1/1     Running     0          21m
istio-system      istio-cleanup-secrets-j4pkx                 0/1     Completed   0          21m
istio-system      istio-egressgateway-676c8546c5-dnwsd        1/1     Running     0          21m
istio-system      istio-galley-5669f7c9b-g774b                1/1     Running     0          21m
istio-system      istio-ingressgateway-5475685bbb-q5f2x       1/1     Running     0          21m
istio-system      istio-pilot-5795d6d695-9klrz                2/2     Running     0          21m
istio-system      istio-policy-7f945bf487-2wh88               2/2     Running     0          21m
istio-system      istio-sidecar-injector-d96cd9459-7knkm      1/1     Running     0          21m
istio-system      istio-statsd-prom-bridge-549d687fd9-lcmb7   1/1     Running     0          21m
istio-system      istio-telemetry-6c587bdbc4-t4jql            2/2     Running     0          21m
istio-system      knative-ingressgateway-7f4477dd99-n9wz2     1/1     Running     0          4m
knative-build     build-controller-7dcc4b7544-rkgwb           1/1     Running     0          4m
knative-build     build-webhook-fb6484576-sr4fk               1/1     Running     0          4m
knative-serving   activator-77d46b585d-b6g8n                  2/2     Running     0          4m
knative-serving   controller-85768cfd45-t8ktc                 1/1     Running     0          4m
knative-serving   webhook-56dd548f8-hjw44                     1/1     Running     0          4m
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;部署预构建的应用&#34;&gt;部署预构建的应用&lt;/h2&gt;

&lt;p&gt;接下来我们试试用一个现有的 Docker 镜像来作为自动伸缩的无状态应用运行到 Knative 上，在当前 Kubernetes 命令空间中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;kubectl create ns helloworld

knctl deploy \
      --namespace helloworld \
      --service hello \
      --image gcr.io/knative-samples/helloworld-go \
      --env TARGET=Rev1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;命令执行后会输出一些信息，表明 &lt;code&gt;hello&lt;/code&gt; 服务已经创建，它的第一个版本 &lt;code&gt;hello-00001&lt;/code&gt; 已经创建，并且被标记为 &lt;code&gt;latest&lt;/code&gt; 和 &lt;code&gt;previous&lt;/code&gt;（第一个版本）。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plain&#34;&gt;Name  hello

Waiting for new revision to be created...

Tagging new revision &#39;hello-00001&#39; as &#39;latest&#39;

Tagging new revision &#39;hello-00001&#39; as &#39;previous&#39;

Succeeded
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们可以用一个 curl 请求，发送到 Knative 的路由层，来调用我们的 &lt;code&gt;hello&lt;/code&gt; 服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ knctl curl --namespace helloworld --service hello
Running: curl &#39;-H&#39; &#39;Host: hello.helloworld.example.com&#39; &#39;http://192.168.64.8:32380&#39;

Hello World: Rev1!

Succeeded
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果没有马上显示 &lt;code&gt;Hello World: Rev1!&lt;/code&gt;，可能是因为你的系统还在下载应用镜像、可以稍后重试。&lt;/p&gt;

&lt;p&gt;我使用的是 Minikube 中的 NodePort Ingress，这意味着我不能设置漂亮的 DNS 路由。以后我会讨论一下公共负载均衡、DNS、Knative 路由以及 &lt;code&gt;https://github.com/cppforlife/kwt&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;knctl deploy&lt;/code&gt; 之后，我们的 Kubernetes 用单 Pod 的形式运行这一服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running   0          15s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Knative 中，服务的版本是一个重要特性。我们可以看到我们的唯一版本的服务正在处理 100% 的流量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ knctl revisions list --namespace helloworld --service hello
Revisions for service &#39;hello&#39;

Name         Tags      Allocated Traffic %  Serving State  Annotations  Age
hello-00001  latest    100%                 Active         -            3m
             previous

1 revisions
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下一步我们使用 &lt;code&gt;knctl deploy&lt;/code&gt; 创建一个新的版本，然后把所有流量分配到新版本：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ knctl deploy \
      --namespace helloworld \
      --service hello \
      --image gcr.io/knative-samples/helloworld-go \
      --env TARGET=Rev2

Name  hello

Waiting for new revision (after revision &#39;hello-00001&#39;) to be created...

Tagging new revision &#39;hello-00002&#39; as &#39;latest&#39;

Tagging older revision &#39;hello-00001&#39; as &#39;previous&#39;

Succeeded
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在请求被发送到了我们的新版本：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ knctl revisions list --namespace helloworld --service hello
Revisions for service &#39;hello&#39;

Name         Tags      Allocated Traffic %  Serving State  Annotations  Age
hello-00002  latest    100%                 Active         -            1m
hello-00001  previous  0%                   Active         -            10m

$ knctl curl --namespace helloworld --service hello
Running: curl &#39;-H&#39; &#39;Host: hello.helloworld.example.com&#39; &#39;http://192.168.64.8:32380&#39;

Hello World: Rev2!
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;部署第二个版本之后，起初两个版本的 Pod 都会持续运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running   0          1m
hello-00002-deployment-7874bf89b8-4b4k5   3/3     Running   0          29s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们会看到 Knative 自动将没有路由指向的版本缩容到 0。&lt;/p&gt;

&lt;h2 id=&#34;路由&#34;&gt;路由&lt;/h2&gt;

&lt;p&gt;下图展示了路由到服务某版本的过程中所涉及到的 &lt;a href=&#34;https://github.com/knative/serving/blob/master/docs/spec/overview.md&#34; target=&#34;_blank&#34;&gt;Knative Serving&lt;/a&gt; 模块：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/object_model.png&#34; alt=&#34;Object Model&#34; /&gt;&lt;/p&gt;

&lt;p&gt;版本是代码、依赖和配置的的只读快照。没有被路由引用的版本会被放弃，其中的 Kubernetes 资源会被删除。&lt;/p&gt;

&lt;p&gt;我们可以看到当前的路由：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ knctl routes list --namespace helloworld
Routes in namespace &#39;helloworld&#39;

Name   Traffic         All Traffic Assigned  Ready  Domain                        Age
hello  100% -&amp;gt; hello:  true                  true   hello.helloworld.example.com  2h
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;缩容至-0&#34;&gt;缩容至 0&lt;/h2&gt;

&lt;p&gt;如果离开五分钟再回来，你会发现 &lt;code&gt;hello-00002&lt;/code&gt; Pod 正在被终结或者已经不见了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS        RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running       0          6m
hello-00002-deployment-7874bf89b8-4b4k5   2/3     Terminating   0          5m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下一次 &lt;code&gt;knctl curl&lt;/code&gt;，Knative 会动态的启动一个 Pod 来满足这一请求。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ knctl curl --namespace helloworld --service hello
$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running   0          8m
hello-00002-deployment-7874bf89b8-kfbm2   3/3     Running   0          10s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我还不太清楚为什么 &lt;code&gt;hello-00001-deployment-...&lt;/code&gt; Pod 没有被缩容和终结。&lt;/p&gt;

&lt;h2 id=&#34;未完待续&#34;&gt;未完待续&lt;/h2&gt;

&lt;p&gt;后续文章中将会尝试：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Knative Build 组件：使用 &lt;code&gt;Dockerfile&lt;/code&gt; 或者 Cloud Foundry buildpack 自动从定制代码构建容器镜像（代码可以保存本地或者 Git 仓库之中）。&lt;/li&gt;
&lt;li&gt;为 Kubernetes 的负载均衡设置 DNS，从而为 Knative 路由和服务提供公共 URL。&lt;/li&gt;
&lt;li&gt;在不同版本之间进行流量分割（例如 10% 到最新版本，90% 到前一版本；然后将 100% 分配给新版本，老版本流量降低到 0%）。&lt;/li&gt;
&lt;li&gt;Knative Eventing：在应用中进行 &lt;a href=&#34;https://github.com/cloudevents/spec&#34; target=&#34;_blank&#34;&gt;CloudEvents&lt;/a&gt;的绑定和分发。让你的服务更加“无服务器”。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;社区&#34;&gt;社区&lt;/h2&gt;

&lt;p&gt;Knative 核心团队有自己的 &lt;a href=&#34;https://knative.slack.com/&#34; target=&#34;_blank&#34;&gt;Knative Slack&lt;/a&gt;，可以在 &lt;a href=&#34;https://slack.knative.dev&#34; target=&#34;_blank&#34;&gt;https://slack.knative.dev&lt;/a&gt; 申请加入。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://groups.google.com/forum/#!forum/knative-dev&#34; target=&#34;_blank&#34;&gt;knative-dev Group&lt;/a&gt; 中包含了总结和提议。&lt;/p&gt;

&lt;h2 id=&#34;鸣谢&#34;&gt;鸣谢&lt;/h2&gt;

&lt;p&gt;感谢 Google 的 &lt;a href=&#34;https://m.chmarny.com/&#34; target=&#34;_blank&#34;&gt;Mark Chmarny&lt;/a&gt;，在 2018 Spring One 上首先回应了我的问题。&lt;/p&gt;

&lt;p&gt;感谢 Pivotal 的 &lt;a href=&#34;https://github.com/cppforlife&#34; target=&#34;_blank&#34;&gt;Dmitriy Kalinin&lt;/a&gt; 花时间帮助我将 Knative 运行起来，并给我展示了他的 &lt;a href=&#34;https://github.com/cppforlife/knctl&#34; target=&#34;_blank&#34;&gt;knctl Knative CLI&lt;/a&gt;，以及 &lt;a href=&#34;https://github.com/cppforlife/kwt&#34; target=&#34;_blank&#34;&gt;kwt Kubernetes Workstation Tools&lt;/a&gt;。相对于 YAML + &lt;code&gt;kubectl&lt;/code&gt; 组合来说，一个好用的客户端工具更能够帮助 Knative 走向实用。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 Kubernetes 和 Istio 环境下进行蓝绿部署</title>
      <link>/post/tutorial-blue-green-deployments-with-kubernetes-and-istio/</link>
      <pubDate>Sat, 20 Oct 2018 00:46:47 +0800</pubDate>
      <guid>/post/tutorial-blue-green-deployments-with-kubernetes-and-istio/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://thenewstack.io/tutorial-blue-green-deployments-with-kubernetes-and-istio/&#34; target=&#34;_blank&#34;&gt;Tutorial: Blue/Green Deployments with Kubernetes and Istio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://thenewstack.io/author/janakiram/&#34; target=&#34;_blank&#34;&gt;Janakiram MSV&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作为一个服务网格系统，Istio 为服务间通信提供稳定性、透明性和安全性方面的保障。不论集群内外的服务，只要其访问目标是网格内的服务，就都会被 Istio 所拦截并进行处理。&lt;/p&gt;

&lt;p&gt;Istio 有很多功能，例如服务间通信的加密、自动的指标记录、访问控制策略、频率限制以及配额等，这里我们仅着眼于最常用的流量管理能力。&lt;/p&gt;

&lt;p&gt;Istio 让 DevOps 团队有能力为内部服务创建智能的路由规则。断路器、超时和重试之类的服务级属性非常容易配置，配置包含蓝绿部署及金丝雀发布的过程也很轻松。&lt;/p&gt;

&lt;p&gt;本文教程用于帮助读者理解配置 Kubernetes + Istio 环境下的蓝绿部署过程。无需很多知识背景，只要理解一些在 Kubernetes 中部署 Pod 和服务的基础概念就好。我们会在 Minikube 和 Istio 中完成示例。&lt;/p&gt;

&lt;p&gt;教程包含四个步骤：安装 Minikube、安装 Istio 并进行验证、安装一个应用的两个版本，最后配置服务的蓝绿部署。我们会使用两个简单的构建好了的镜像，分别作为蓝（v1）、绿（v2）两个版本。&lt;/p&gt;

&lt;h2 id=&#34;步骤-1-安装-minikube&#34;&gt;步骤 1：安装 Minikube&lt;/h2&gt;

&lt;p&gt;为了降低依赖，我们会使用 Minikube 作为测试平台。因为需要自定义配置，所以要删除已经存在的配置，并使用额外参数重新启动集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;minikube start --memory=8192 --cpus=4 --kubernetes-version=v1.10.0 \
--extra-config=controller-manager.cluster-signing-cert-file=&amp;quot;/var/lib/localkube/certs/ca.crt&amp;quot; \
--extra-config=controller-manager.cluster-signing-key-file=&amp;quot;/var/lib/localkube/certs/ca.key&amp;quot; \
--vm-driver=virtualbox
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;要在 Minikube 上运行 Istio，需要至少 8G 内存和 4 个 CPU 核心。等集群启动：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/b1cfb731-istio-0-1024x431.png&#34; alt=&#34;minikube startup&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;步骤-2-安装-istio&#34;&gt;步骤 2：安装 Istio&lt;/h2&gt;

&lt;p&gt;Kubernetes 集群成功启动之后，就可以安装 Istio 了。用下面的步骤完成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;curl -L https://git.io/getLatestIstio | sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在运行上述命令的目录中会发现一个 &lt;code&gt;istio-1.0.2&lt;/code&gt; 目录，可以把 &lt;code&gt;istio-1.0.2/bin&lt;/code&gt; 目录加入 &lt;code&gt;PATH&lt;/code&gt; 变量，方便后面的命令执行过程。&lt;/p&gt;

&lt;p&gt;由于我们在 Minikube 环境下运行的 Istio，所以我们要在下一步进行之前，要把 Ingress Gateway 服务从 &lt;code&gt;LoadBalancer&lt;/code&gt; 改为 &lt;code&gt;NodePort&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;打开文件 &lt;code&gt;istio-1.0.2/install/kubernetes/istio-demo.yaml&lt;/code&gt;，查找并替换：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/2a64731e-istio-1.png&#34; alt=&#34;Changing Service Type&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Istio 中包含了很多 CRD，可以帮用户来进行虚拟服务、规则、网关以及其他对象的管理。在部署服务网格之前首先要部署一下这些 CRD：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl apply -f install/kubernetes/helm/istio/templates/crds.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后，在 Kubernetes 中安装 Istio：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl apply -f install/kubernetes/istio-demo.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的步骤会创建新的命名空间（&lt;code&gt;istio-system&lt;/code&gt;）：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/38e93379-istio-3.png&#34; alt=&#34;kubectl get ns&#34; /&gt;&lt;/p&gt;

&lt;p&gt;会看到这里还有很多服务：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/38e93379-istio-3-1024x524.png&#34; alt=&#34;kubectl get svc&#34; /&gt;&lt;/p&gt;

&lt;p&gt;稍候片刻，会看到很多 Pod：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/846a8174-istio-4-1024x498.png&#34; alt=&#34;kubectl get po&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Istio 如果成功部署，所有这些 Pod 只能是 &lt;code&gt;Running&lt;/code&gt; 或者 &lt;code&gt;Completed&lt;/code&gt; 状态。&lt;/p&gt;

&lt;p&gt;下一步就要准备用于蓝绿部署的应用了。&lt;/p&gt;

&lt;h2 id=&#34;步骤-3-安装同一应用的两个版本&#34;&gt;步骤 3：安装同一应用的两个版本&lt;/h2&gt;

&lt;p&gt;为了展示应用的不同版本，我构建了基于 Nginx 的简单镜像 - &lt;code&gt;janakiramm/myapp:v1&lt;/code&gt; 和 &lt;code&gt;janakiramm/myapp:v2&lt;/code&gt;。部署之后，会展示蓝色或者绿色的背景。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Service
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  type: ClusterIP
  ports:
  - port: 80
    name: http
  selector:
    app: myapp
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myapp-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: myapp
        version: v1
    spec:
      containers:
      - name: myapp
        image: janakiramm/myapp:v1
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myapp-v2
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: myapp
        version: v2
    spec:
      containers:
      - name: myapp
        image: janakiramm/myapp:v2
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也可以在 &lt;a href=&#34;https://gist.github.com/janakiramm/123dc67b78ef187a109e7f928d6a6878&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt; 上看到这些代码。&lt;/p&gt;

&lt;p&gt;接着就要创建 YAML 文件来定义 v1 和 v2 服务了。注意 Pod 标签的差异代表了不同的版本 —— &lt;code&gt;app&lt;/code&gt; 保持一致，但 &lt;code&gt;version&lt;/code&gt; 是不同的。这样一来，Istio 就会认为这是同一应用的不同版本。&lt;/p&gt;

&lt;p&gt;而服务中的选择器定义只针对 &lt;code&gt;app&lt;/code&gt; 标签进行设置，也就是说不同版本的 Pod 都会参与这一服务。&lt;/p&gt;

&lt;p&gt;用 &lt;code&gt;kubectl&lt;/code&gt; 创建 &lt;code&gt;Service&lt;/code&gt; 和 &lt;code&gt;Deployment&lt;/code&gt;。注意这个简单的应用对 Istio 一无所知。Istio 和应用的唯一可见的连接就是标签：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl apply -f myapp.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/7ddbc930-istio-5.png&#34; alt=&#34;apply -f yaml&#34; /&gt;&lt;/p&gt;

&lt;p&gt;配置 Istio 路由之前，首先检查一下应用的版本。可以使用端口转发的方式来访问 Pod。&lt;/p&gt;

&lt;p&gt;要访问应用的 &lt;code&gt;v1&lt;/code&gt; 版本，可以运行下面的命令，然后访问 &lt;code&gt;localhost:8080&lt;/code&gt;，验证完成之后，按 &lt;code&gt;CTRL+C&lt;/code&gt; 结束端口映射命令。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl port-forward deployment/myapp-v1 8080:80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/b5088c7c-istio-6-300x183.png&#34; alt=&#34;app image blue&#34; /&gt;&lt;/p&gt;

&lt;p&gt;要访问应用的 &lt;code&gt;v2&lt;/code&gt; 版本，可以运行下面的命令，然后访问 &lt;code&gt;localhost:8081&lt;/code&gt;，验证完成之后，按 &lt;code&gt;CTRL+C&lt;/code&gt; 结束端口映射命令。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl port-forward deployment/myapp-v2 8081:80
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/a187169b-istio-7-300x183.png&#34; alt=&#34;app image green&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;步骤-4-配置蓝绿部署&#34;&gt;步骤 4：配置蓝绿部署&lt;/h2&gt;

&lt;p&gt;我们的目标是在不停机的情况下，让流量选择性的进入某一版本。为了完成这一目的，就需要告知 Istio 根据权重进行路由。完成这一任务需要三个对象：&lt;/p&gt;

&lt;h3 id=&#34;gateway&#34;&gt;Gateway&lt;/h3&gt;

&lt;p&gt;Istio Gateway 描述了网格边缘的负载均衡组件，用于 HTTP/TCP 连接的接收和发出。定义中包含一组要开放的端口、使用的协议、负载均衡的 SNI 等。下面的定义中我们将 Gateway 指向 Istio 部署过程中建立的缺省的 Ingress Gategeway。&lt;/p&gt;

&lt;p&gt;用 Kubernetes 的方式创建 Gateway 对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: app-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - &amp;quot;*&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;目标规则&#34;&gt;目标规则&lt;/h3&gt;

&lt;p&gt;Istio &lt;code&gt;DestinationRule&lt;/code&gt; 定义了在一个服务成为路由目标之后的行为。注意一下这一规则中是如何通过标签来对 Kubernetes 的原生 Deployment 进行区分的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: myapp
spec:
  host: myapp
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;虚拟服务&#34;&gt;虚拟服务&lt;/h3&gt;

&lt;p&gt;虚拟服务中定义了一组流量路由规则，在其中的 &lt;code&gt;host&lt;/code&gt; 被访问时就会触发。每个路由规则中都定义了对某一协议进行匹配的标准。如果流量匹配这一标准，那么就发送给对应的区分了版本的目标服务。&lt;/p&gt;

&lt;p&gt;下面的定义中我们定义两个版本的服务权重都是 50，也就是说流量会在版本间进行平均分配：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
  - &amp;quot;*&amp;quot;
  gateways:
  - app-gateway
  http:
    - route:
      - destination:
          host: myapp
          subset: v1
        weight: 50
      - destination:
          host: myapp
          subset: v2
        weight: 50
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所有这些都可以定义在同一个 YAML 文件中，然后用 &lt;code&gt;kubectl&lt;/code&gt; 提交给集群，同样可以在 &lt;a href=&#34;https://gist.github.com/janakiramm/35078d95730745caa62f81d917d6d553&#34; target=&#34;_blank&#34;&gt;Gtihub&lt;/a&gt; 中获取这一文件。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl apply -f app-gateway.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/b64331f6-istio-8-300x75.png&#34; alt=&#34;app gateway&#34; /&gt;&lt;/p&gt;

&lt;p&gt;接下来就可以尝试访问这一服务了。因为我们使用的是 NodePort 模式的服务，所以就需要首先判断一下 Ingress Gateway 所在的端口。&lt;/p&gt;

&lt;p&gt;运行下面的命令来访问 MiniKube 的 Ingress 端口。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ export INGRESS_HOST=$(minikube ip)

$ export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=&#39;{.spec.ports[?(@.name==&amp;quot;http2&amp;quot;)].nodePort}&#39;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;从浏览器访问这个 URL，会看到流量被均等的在蓝色和绿色版本之间进行分配。&lt;/p&gt;

&lt;p&gt;也可以在终端里面查看命令结果。运行下面的命令会看到 V1 和 V2 的响应：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;while : ;do export GREP_COLOR=&#39;1;33&#39;;curl -s  192.168.99.100:31380 \
 |  grep --color=always &amp;quot;V1&amp;quot; ; export GREP_COLOR=&#39;1;36&#39;;\
 curl -s  192.168.99.100:31380 \
 | grep --color=always &amp;quot;vNext&amp;quot; ; sleep 1; done
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/840d897c-istio-9-1024x674.png&#34; alt=&#34;curl&#34; /&gt;&lt;/p&gt;

&lt;p&gt;上面的命令会循环运行，我们可以返回编辑 &lt;code&gt;gateway.yaml&lt;/code&gt;，修改其中的权重分配。把 V1 的权重设置为 0，V2 的权重设置为 100.&lt;/p&gt;

&lt;p&gt;把新的定义提交到 Istio。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ istioctl replace -f app-gateway.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/0b6f6f9e-istio-10-300x59.png&#34; alt=&#34;replace&#34; /&gt;&lt;/p&gt;

&lt;p&gt;更新权重之后，V2 的响应比例会提升到 100%。这一结果会体现在输出之中：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/82d4e75d-istio-11.png&#34; alt=&#34;v2-100%&#34; /&gt;&lt;/p&gt;

&lt;p&gt;可以继续对权重进行修改，查看路由的动态变化过程。&lt;/p&gt;

&lt;p&gt;流量管理只是 Istio 的一个功能，后续文章中尝试更多其他特性。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 1.12 中的 RuntimeClass</title>
      <link>/post/runtimeclass-in-kubernetes/</link>
      <pubDate>Thu, 11 Oct 2018 10:49:46 +0800</pubDate>
      <guid>/post/runtimeclass-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2018/10/10/kubernetes-v1.12-introducing-runtimeclass/&#34; target=&#34;_blank&#34;&gt;Kubernetes v1.12: Introducing RuntimeClass&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;起初，Kubernetes 只支持运行于 Docker 容器中的 Linux 本地应用。Kubernetes 1.3 中，&lt;a href=&#34;https://kubernetes.io/blog/2016/07/rktnetes-brings-rkt-container-engine-to-kubernetes/&#34; target=&#34;_blank&#34;&gt;rtk&lt;/a&gt; 为首的其他运行时支持开始逐步浮现，促成了&lt;a href=&#34;https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/&#34; target=&#34;_blank&#34;&gt;容器运行时（CRI）&lt;/a&gt;的诞生，更多的项目也因此加入了这一行列 ：&lt;a href=&#34;https://katacontainers.io/&#34; target=&#34;_blank&#34;&gt;Kata Container&lt;/a&gt; 和 &lt;a href=&#34;https://github.com/google/gvisor&#34; target=&#34;_blank&#34;&gt;gVisor&lt;/a&gt; 实现了更好的工作负载隔离；Kubernetes 的 Windows 支持也一直在&lt;a href=&#34;https://kubernetes.io/blog/2018/01/kubernetes-v19-beta-windows-support/&#34; target=&#34;_blank&#34;&gt;稳步发展&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;不同的容器运行时面向不同的使用场景，也就产生了在同一集群中使用混合运行时的需要。但是这所有不同的运行容器的方式都带来了一些亟待处理的问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用户如何列出、并为工作负载选定合适的运行时？&lt;/li&gt;
&lt;li&gt;如何保证让 Pod 被调度到支持指定运行时的节点上？&lt;/li&gt;
&lt;li&gt;各种运行时都支持什么样的特性？如何让用户了解到这其中的兼容问题？&lt;/li&gt;
&lt;li&gt;多种运行时的不同资源开销如何应对？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;RuntimeClass&lt;/code&gt; 为此而来。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-1-12-中的-runtimeclass&#34;&gt;Kubernetes 1.12 中的 RuntimeClass&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;RuntimeClass&lt;/code&gt; 在 Kubernetes 1.12 中实现，目前为 Alpha 阶段。初始阶段的焦点是提供一个对运行时进行选择的 API，并且为解决其它多运行时方面的问题进行了一些尝试。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RuntimeClass&lt;/code&gt; 资源对 Kubernetes 集群上的容器运行时进行了描述。集群安装程序用 &lt;code&gt;RuntimeClass&lt;/code&gt; 对运行时进行安装、设置和定义。目前 &lt;code&gt;RuntimeClassSpec&lt;/code&gt; 包含一个字段 &lt;code&gt;RuntimeHandler&lt;/code&gt;。运行于节点上的 CRI 会对 &lt;code&gt;RuntimeHandler&lt;/code&gt; 进行解释，将其映射为实际的运行时配置。&lt;code&gt;PodSpec&lt;/code&gt; 也随之进行了扩展，加入了一个 &lt;code&gt;RuntimeClassName&lt;/code&gt; 字段，这个字段的值代表运行该 Pod 所需的 &lt;code&gt;RuntimeClass&lt;/code&gt; 的名称。&lt;/p&gt;

&lt;p&gt;为什么 &lt;code&gt;RuntimeClass&lt;/code&gt; 是个 Pod 级的概念？Kubernetes 资源模型能够在 Pod 中的不同容器之间共享某些资源。如果组成 Pod 的不同容器具有不同的资源模型，会对资源共享造成很大的挑战。例如在不同虚拟机之间共享 &lt;code&gt;loopback&lt;/code&gt; 适配器是极其困难的，但是在同一 Pod 中的两个容器之间进行通信时，这是个非常普遍的需要。&lt;/p&gt;

&lt;h2 id=&#34;下一步&#34;&gt;下一步？&lt;/h2&gt;

&lt;p&gt;要向控制面呈现运行时属性，&lt;code&gt;RuntimeClass&lt;/code&gt; 资源是个很重要的基础。例如要在多种不同运行时 Node 组成的集群中实现调度支持，我们可能需要在 &lt;code&gt;RuntimeClass&lt;/code&gt; 定义中实现 &lt;a href=&#34;https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity&#34; target=&#34;_blank&#34;&gt;NodeAffinity&lt;/a&gt;。&lt;a href=&#34;https://docs.google.com/document/d/1EJKT4gyl58-kzt2bnwkv08MIUZ6lkDpXcxkHqCvvAp4/preview&#34; target=&#34;_blank&#34;&gt;Pod Overhead proposal&lt;/a&gt; 在这方面做出了一些早期尝试，和 &lt;code&gt;RuntimeClass&lt;/code&gt; 非常匹配，未来会逐步进行跟进。&lt;/p&gt;

&lt;p&gt;目前还提出了很多其它的 &lt;code&gt;RuntimeClass&lt;/code&gt; 扩展，会逐步进行进一步的研究和开发。正在考虑的扩展包括：&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;为 Pod 的需求提供合适的 &lt;code&gt;RuntimeClass&lt;/code&gt;。例如指定运行时属性，让系统自行对 &lt;code&gt;RuntimeClass&lt;/code&gt; 进行匹配，从而避免显式指定 &lt;code&gt;RuntimeClass&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;至少到 2019 年。&lt;code&gt;RuntimeClass&lt;/code&gt; 的开发工作都会保持活跃，我们很高兴，从 Kubernetes 1.12 开始，这一功能以 Alpha 的形态成功面世。&lt;/p&gt;

&lt;h2 id=&#34;还有更多&#34;&gt;还有更多&lt;/h2&gt;

&lt;p&gt;作为一个 Alpha 功能，还需要一些额外的设置步骤才能够使用 &lt;code&gt;RuntimeClass&lt;/code&gt;。详情请参考 &lt;a href=&#34;https://kubernetes.io/docs/concepts/containers/runtime-class/#runtime-class&#34; target=&#34;_blank&#34;&gt;RuntimeClass 文档&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/kubernetes/community/blob/master/keps/sig-node/0014-runtime-class.md&#34; target=&#34;_blank&#34;&gt;RuntimeClass Kubernetes ENhancement Proposal&lt;/a&gt; 之中包含了更多的设计细节。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://docs.google.com/document/d/1fe7lQUjYKR0cijRmSbH_y0_l3CYPkwtQa5ViywuNo8Q/preview&#34; target=&#34;_blank&#34;&gt;Sandbox Isolation Level Decision&lt;/a&gt; 一文介绍了将此方案落地到 Pod 级的思考过程。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubespy：实时查看 Kubernetes 资源</title>
      <link>/post/intro-kubespy/</link>
      <pubDate>Wed, 10 Oct 2018 17:00:41 +0800</pubDate>
      <guid>/post/intro-kubespy/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://github.com/pulumi/kubespy&#34; target=&#34;_blank&#34;&gt;kubespy: tools for observing Kubernetes resources in real time
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一个 &lt;code&gt;Pod&lt;/code&gt; 启动时会发生些什么？一个 &lt;code&gt;Service&lt;/code&gt; 拿到公共 IP 之前又会干点啥？&lt;code&gt;Deployment&lt;/code&gt; 的状态是怎么切换的？&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubespy&lt;/code&gt; 是个小工具，能够轻松的实时查看 Kubernetes 集群里的资源变化。这一工具源自于 &lt;a href=&#34;https://www.pulumi.com/kubernetes/&#34; target=&#34;_blank&#34;&gt;Pulumi CLI&lt;/a&gt; ，其中对 Kubernetes Deployment 的预测做出了一些常识。可以在任何时机运行 &lt;code&gt;kubespy&lt;/code&gt;，它会持续对 Kubernetes 资源进行监控和报告，直到杀掉进程为止。&lt;/p&gt;

&lt;h2 id=&#34;例如&#34;&gt;例如&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kubespy trace deployment nginx&lt;/code&gt; 会对集群中名为 &lt;code&gt;nginx&lt;/code&gt; 的 &lt;code&gt;Deployment&lt;/code&gt; 对象进行跟踪，并将状态信息进行聚合，生成较高级的概要，并且进行实时更新。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/trace-deployment-rollout.gif&#34; alt=&#34;trace deployment rollout&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubespy status v1 Pod nginx&lt;/code&gt; 会等待创建一个叫做 &lt;code&gt;nginx&lt;/code&gt; 的 &lt;code&gt;Pod&lt;/code&gt;，然后持续刷新其状态，并用高亮语法的 JSON 形式展现出来。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/status.gif&#34; alt=&#34;status&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;获取&lt;a href=&#34;https://github.com/pulumi/kubespy/releases&#34; target=&#34;_blank&#34;&gt;最新版本&lt;/a&gt;，重命名为 &lt;code&gt;kubespy&lt;/code&gt;，&lt;code&gt;chmod -x kubespy&lt;/code&gt; 并将其拷贝到可执行路径之中（例如 &lt;code&gt;/usr/local/bin&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;或者&lt;/p&gt;

&lt;p&gt;安装 &lt;a href=&#34;https://golang.org/&#34; target=&#34;_blank&#34;&gt;Go&lt;/a&gt; 1.11 以上版本，然后运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;export GO111MODULE=on

# If $GOBIN is not on your path, you&#39;ll need to install the library elsewhere.
go install github.com/pulumi/kubespy
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;两种方式都能够完成安装。&lt;/p&gt;

&lt;h2 id=&#34;用法&#34;&gt;用法&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kubespy&lt;/code&gt; 有三个子命令：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;status &amp;lt;apiVersion&amp;gt; &amp;lt;kind&amp;gt; [&amp;lt;namespace&amp;gt;/]&amp;lt;name&amp;gt;&lt;/code&gt;：实时跟踪任意 Kubernetes 对象的 &lt;code&gt;.status&lt;/code&gt; 字段，并用 JSON Diff 的方式进行展示。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;changes &amp;lt;apiVersion&amp;gt; &amp;lt;kind&amp;gt; [&amp;lt;namespace&amp;gt;/]&amp;lt;name&amp;gt;&lt;/code&gt;：实时获取一个 Kubernetes 资源的所有字段的所有变化，用 JSON Diff 进行展示。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;trace &amp;lt;kind&amp;gt; [&amp;lt;namespace&amp;gt;/]&amp;lt;name&amp;gt;&lt;/code&gt;：对复杂 Kubernetes 资源进行跟踪，在整个集群范围内进行聚合，生成汇总信息，并保持实时更新。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Knative：在 Kubernetes 上构建可移植 Serverless 平台</title>
      <link>/post/knative-enables-portable-serverless/</link>
      <pubDate>Thu, 20 Sep 2018 17:13:03 +0800</pubDate>
      <guid>/post/knative-enables-portable-serverless/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://thenewstack.io/knative-enables-portable-serverless-platforms-on-kubernetes-for-any-cloud/&#34; target=&#34;_blank&#34;&gt;Knative Enables Portable Serverless Platforms on Kubernetes, for Any Cloud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://thenewstack.io/author/dan-baskette/&#34; target=&#34;_blank&#34;&gt;Dan Baskette&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;p&gt;但是我们注意到，开发团队在进行基于 Kubernetes 的应用部署时常有困扰。Kubernetes 毕竟只会推送容器——要想推送应用代码或者 Function，很明显就不是 Kubernetes 的能力所在了。&lt;/p&gt;

&lt;p&gt;这样一来，就有不少厂商以 K8S 作为基础设施，展开了高级抽象方面的竞争。这也是 &lt;a href=&#34;http://github.com/knative&#34; target=&#34;_blank&#34;&gt;Knative&lt;/a&gt; 的着眼点。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/kelseyhightower&#34; target=&#34;_blank&#34;&gt;Kelsey Hightower&lt;/a&gt;：Kubernetes 是一个用来构建平台的平台。它是起跑线，不是目的地。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;function-是值得注意的下一次抽象&#34;&gt;Function 是值得注意的下一次抽象&lt;/h2&gt;

&lt;p&gt;读者可能已经注意到分布式系统世界的新晋成员：FaaS（Functions/Serverless）。Function 是一种新的抽象方式，让开发人员能够轻松的运行部署代码片段，并具备根据事件进行伸缩的能力。&lt;/p&gt;

&lt;p&gt;这对开发人员来说是非常有吸引力的。为什么？把所有的基础设施和应用启动之前的事件处理都抽象之后，开发人员能够完全专注于解决如何使用 Function 的代码处理事件的问题。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://storage.googleapis.com/cdn.thenewstack.io/media/2018/07/26b7e88b-danb1.png&#34; alt=&#34;抽象策略&#34; /&gt;&lt;/p&gt;

&lt;p&gt;天下自然是没有免费的午餐了，FaaS 的问题在哪里呢？&lt;/p&gt;

&lt;p&gt;市场上有很多 FaaS 方案，每个都是独一无二的，他们有自己的 Function 触发方式，自己的可接受事件格式范围，独特的伸缩功能以及从各自角度触发，为开发人员作出的各种抽象。&lt;/p&gt;

&lt;p&gt;如果有要用到的抽象，可以寄希望于云供应商将其打包并为你提供服务。Azure Functions、Lambda 以及 Google Cloud Function 就是这样工作的：根据事件运行 Function 代码，按需伸缩。这类产品按照用量进行计费——根据调用量收取费用。&lt;/p&gt;

&lt;p&gt;开源社区的开发者们也加入了 FaaS 的盛宴，OpenFaaS、Fission、Kubeless 以及 Project Riff 这些项目都是构建在 Kubernetes 之上的 FaaS。这一共同的基础，也有了大同小异的产品。&lt;/p&gt;

&lt;p&gt;在三个核心领域，每个项目都有些许差异：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;都有自己的构建服务，也就是把 Function 进行容器式构建和部署的功能。&lt;/li&gt;
&lt;li&gt;都有一种按调用需要进行扩容（或者缩容）的实现。&lt;/li&gt;
&lt;li&gt;都提供了根据事件调用 Function 的能力，事件可能是 HTTP 或者是事件中间件的发布、订阅方式。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些细微差异会造成平台采用的巨大障碍。在企业开发者眼里，这一领域功能破碎，竞品众多。所以只能静观其变。&lt;/p&gt;

&lt;h2 id=&#34;knative-适时出现&#34;&gt;Knative 适时出现&lt;/h2&gt;

&lt;p&gt;Google 看到这种碎片化的现状，也注意到了开发人员在 Kubernetes 上进行 Function 开发的过程中对通用工具集的需求。&lt;a href=&#34;http://pivotal.io/knative&#34; target=&#34;_blank&#34;&gt;Knative&lt;/a&gt; 就是基于这种需求产生的。&lt;/p&gt;

&lt;p&gt;Knative 是一个开源软件层，帮助云服务供应商和企业平台在任意云上为开发者提供 Serverless 体验。&lt;/p&gt;

&lt;p&gt;Pivotal 也身在其中，不但向 Knative 贡献了来自 &lt;a href=&#34;https://projectriff.io/&#34; target=&#34;_blank&#34;&gt;riff&lt;/a&gt; 项目的事件模型，还和 Google 一起，在其它方面贡献了开发人员和代码。我们为这一项目的未来欢欣鼓舞，将 riff 和 Knative 结合在一起，酝酿成我们的新项目 &lt;a href=&#34;https://pivotal.io/platform/pivotal-function-service&#34; target=&#34;_blank&#34;&gt;Pivotal Function Service&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;所以对于 Knative 来说，还需要知道点什么呢？这个项目使用 Kubernetes 作为容器编排层。它使用大家熟知的 Kubernetes 对象（Pod、Replica Set 以及 Deployment）构建应用。Istio？是的，Knative 使用 Istio 来进行网格内的路由以及 Ingress 入口管理。&lt;/p&gt;

&lt;p&gt;但是仅仅有 Kubernetes 和 Istio 还是不够的。因此 Knative 同时还引入了三个松耦合的组件，协同对外提供一个完整的 Serverless 平台：Build、Eventing 以及 Serving。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build：提供了一个可插入模型，用于从源码构建容器。&lt;/li&gt;
&lt;li&gt;Eventing：让应用或者 Function 发布到或订阅事件流，事件流包括 Google Cloud Pub/Sub 以及 Apache Kafka。&lt;/li&gt;
&lt;li&gt;Serving：为应用或 Function 提供运行和扩容以及缩容至 0 的能力。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上述元素融合形成的 Knative 又有何神通？它提供了一种较为简化的部署和运行 function 的方式，包括这些模式：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;从源码构建应用和 Function。&lt;/li&gt;
&lt;li&gt;包含多种构建方法（Cloud Foundry Buildpacks、Bazel、Kaniko、Dockerfiles，并可以扩展支持其他方式）。&lt;/li&gt;
&lt;li&gt;开发者能够轻松部署新的（可路由的）应用和 Function。&lt;/li&gt;
&lt;li&gt;允许应用的不间断升级。&lt;/li&gt;
&lt;li&gt;应用实例的自动伸缩。&lt;/li&gt;
&lt;li&gt;把事件绑定到 Function、应用或者容器上。&lt;/li&gt;
&lt;li&gt;当发生 HTTP 请求时触发 Function。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;稍微深入一点看看这几个组件。&lt;/p&gt;

&lt;h2 id=&#34;build-源码到容器的弹性和可扩展过程&#34;&gt;Build：源码到容器的弹性和可扩展过程&lt;/h2&gt;

&lt;p&gt;开发人员编写源码。Kubernetes 操作容器。如何完成联动？Cloud Foundry 使用 buildpack 来完成这一场景。Knative 提供一个插件模型来完成从代码到容器的构建过程。这一模型通过 CRD 实现，也就是一组 Kubernetes API 对象。这种方式提供了一个构建块，能够作为一个 CI/CD 之类的更大系统的一部分，完成源码的构建。&lt;/p&gt;

&lt;p&gt;Knative 的 Build 组件包含 4 个主要组成部分。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;描述如何获取待构建的源码。位置在 &lt;code&gt;/workspace&lt;/code&gt; 卷中存储，这个内容会在后面的步骤中沿用。通常情况下，源码会保存在 git gcs 之类的版本控制系统中，也可以用自定义容器来访问源码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;步骤或模板&lt;/strong&gt;：这是构建容器的实际工作。这个过程简单说来就是根据 Build 规范完成一系列步骤。换句话说，这一过程由一组可插接构建器组成，被设计用来从源码构建容器，目前这个模型支持五种构建模板，提供了可共享的构建过程：&lt;a href=&#34;https://docs.cloudfoundry.org/buildpacks/&#34; target=&#34;_blank&#34;&gt;Cloud Foundry Buildpacks&lt;/a&gt;、&lt;a href=&#34;https://cloud.google.com/container-builder/docs/&#34; target=&#34;_blank&#34;&gt;Google Container Builder&lt;/a&gt;、Bazel、Kaniko 以及 Jib。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Account&lt;/strong&gt;：用来运行构建过程的账号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存储卷&lt;/strong&gt;：可以定义多个卷，来提供对构建步骤的支持。这些卷可以有很多用途，例如共享 Secret 或者在多个步骤间提供缓存。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;serving-按需伸缩以及版本为基础的高级运维&#34;&gt;Serving：按需伸缩以及版本为基础的高级运维&lt;/h2&gt;

&lt;p&gt;自动化升级了开发者的工作流。Serving 的自动化范围覆盖了从容器到运行中的 Function 部分。它还提供了容器的快速部署，以及根据进入请求完成扩容到 N 或缩容到 0 的能力支持。Istio 在版本之间进行路由，这使得不间断升级、蓝绿部署、金丝雀测试以及回滚都成为了可能。&lt;/p&gt;

&lt;p&gt;Serving 包含了四个 CRD：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;管理应用和 Function 的生命周期以及提供控制点。它可以处理对象的生成，来保障应用或者 Function 的任何版本更新都具备网络路由、配置以及版本支持。&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;&lt;img src=&#34;images/7346ed16-danb2.png&#34; alt=&#34;serving&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;eventing-把订阅-发布操作进行抽象-简化开发人员工作&#34;&gt;Eventing：把订阅/发布操作进行抽象，简化开发人员工作&lt;/h2&gt;

&lt;p&gt;Function 的基本存在价值就是用来响应事件。FaaS 项目和受管服务的区别就是事件的接收以及消费方式。Knative Eventing 组件用来对事件系统的后端进行抽象，从而解放开发人员。开发人员无需了解消息平台、不用关注数据复制等问题。&lt;/p&gt;

&lt;p&gt;Knative 提供了 CRD 用于事件的生产和消费。Eventing 组件由两类 CRD 组成：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Channel&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;发布/订阅模型中发布者发送消息的目标。一般来说，Channel 是一组位置用于获取或存储事件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bus&lt;/strong&gt;：Channel 的后端。这是为事件提供消息平台支持的底层，可以是 Google Cloud PubSub、Apache Kafka 以及 RabbitMQ 等。&lt;/li&gt;
&lt;li&gt;这些结合起来告知 Knative 服务，特定 Channel 的消息会被哪个应用或者 Function 消费。这是应用和 Function 的入口地址。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feeds&lt;/strong&gt;：事件携带的附件。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;images/88636989-danb3.png&#34; alt=&#34;eventing&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;试用你能掌控的最高级抽象&#34;&gt;试用你能掌控的最高级抽象&lt;/h2&gt;

&lt;p&gt;Knative 是一个全新事物，但是已经有了很多资源可供学习和参考。企业开发软件数量暴涨，意味着典型情况下，公司都会谋求试用应用平台、容器编排以及 Function。Pivotal 希望在所有不同抽象中驱动开源软件的发展。Cloud Foundry、Kubernetes 以及 Knative 会成为大公司的软件构建和运行过程中的主要推手。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/knative/docs&#34; target=&#34;_blank&#34;&gt;Knative 文档&lt;/a&gt;是该项目的主要信息源。每个组件都在仓库中有自己的一席之地，让用户可以跟进最新进展。&lt;/li&gt;
&lt;li&gt;可以阅读 &lt;a href=&#34;https://content.pivotal.io/blog&#34; target=&#34;_blank&#34;&gt;Pivotal 博客&lt;/a&gt;，Ryan Morgan 在其中发布了关于 Pivotal 在 Knative 项目中贡献的相关内容，会涉及企业应用 Serverless 的更多案例。&lt;/li&gt;
&lt;li&gt;在 Google Cloud 也有很多资料：

&lt;ul&gt;
&lt;li&gt;Knative &lt;a href=&#34;https://cloud.google.com/knative&#34; target=&#34;_blank&#34;&gt;概览页面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Knative &lt;a href=&#34;https://cloudplatform.googleblog.com/2018/07/bringing-the-best-of-serverless-to-you.html&#34; target=&#34;_blank&#34;&gt;博客&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;如果想要知道 &lt;a href=&#34;https://projectriff.io/&#34; target=&#34;_blank&#34;&gt;riff 项目&lt;/a&gt; 的信息，官方网站是最好的起步地点。其中包含了所有的文档和对 &lt;a href=&#34;https://github.com/projectriff/riff&#34; target=&#34;_blank&#34;&gt;riff 仓库&lt;/a&gt;的引用。&lt;/li&gt;
&lt;li&gt;想要了解更多？&lt;a href=&#34;https://springoneplatform.io/2018/sessions&#34; target=&#34;_blank&#34;&gt;SpringOne 平台有一套 Serverless 课程&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 如何走向统一调度之路</title>
      <link>/post/how-k8s-transforming-into-scheduler/</link>
      <pubDate>Tue, 18 Sep 2018 22:04:06 +0800</pubDate>
      <guid>/post/how-k8s-transforming-into-scheduler/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://thenewstack.io/how-kubernetes-is-transforming-into-a-universal-scheduler/&#34; target=&#34;_blank&#34;&gt;How Kubernetes Is Transforming into a Universal Scheduler&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://thenewstack.io/author/janakiram/&#34; target=&#34;_blank&#34;&gt;Janakiram MSV&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;计算机科学里，调度指的是一种能够为作业分配满足其执行所需资源的方法。IBM 在 60 年代的 S/360 中首次提出这一概念，可以说是年代久远了。&lt;/p&gt;

&lt;p&gt;对所有存在资源需求的作业来说，调度都是至关重要的。在操作系统的上下文中，作业可能是个简单的程序，资源可能是 CPU 核心。类似的，操作系统中的调度器可能就只是一些用于操作线程或信号的代码。&lt;/p&gt;

&lt;p&gt;分布式计算将调度器的疆土从内部的进程和线程扩展到了物理机集群之中。90 年代中，Corba、DCOM 以及 J2EE 等分布式平台应运而生，在应用服务器集群内发展了各自的调度组件。&lt;/p&gt;

&lt;p&gt;再后来，出现了 Amazon EC2、Azure Fabric 以及 OpenStack Nova 这样的 IaaS 平台，这些平台的控制平面完成了对运行于物理机基础之上的虚拟机的调度工作。根据资源需求将虚拟机实例安置在合适的物理机上。&lt;/p&gt;

&lt;p&gt;基于 Apache Hadoop 和 MapReduce 算法的大数据工作负载对调度算法非常依赖。Hadoop 的文件系统 HDFS 就用于保障集群上的节点能够访问到数据集。这一架构聚焦于资源的的稳定性和可用性保障。&lt;/p&gt;

&lt;p&gt;Cloud Foundry 和 Heroku 这样的 PaaS 实现中包含了设计精密的调度逻辑，用来为服务提供隔离的环境。每个服务都被打包，部署在虚拟或物理服务器的执行环境之中。&lt;/p&gt;

&lt;p&gt;横空出世的容器，强迫业界重新审视资源调度器的设计。新一代调度器的设计理念更加重视简单性和伸缩性。传统应用服务器面对的是少量服务器，而容器管理平台要管理的容器工作节点数量可能从几台到几千台。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/15fb358d-k8s-sched-0.jpg&#34; alt=&#34;几种不同的调度器&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 和 Mesosphere 是当代资源调度器的代表。它们的设计对底层基础设施进行了抽象，用透明的方式为用户提供调度服务。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-中的调度&#34;&gt;Kubernetes 中的调度&lt;/h2&gt;

&lt;p&gt;Kubernetes 平台中，调度器是一个关键组件。它在主节点上运行，和 API Server 以及 Controller 紧密合作。调度器的核心任务就是对 Pod 和 Node 进行撮合。可以在 &lt;a href=&#34;https://thenewstack.io/kubernetes-an-overview/&#34; target=&#34;_blank&#34;&gt;Kubernetes architecture&lt;/a&gt; 一文中透彻的了解其架构。&lt;/p&gt;

&lt;p&gt;调度器会在多个方面对可用资源进行评估，从而为 Pod 分配合适的节点。另外还可以通过对节点亲和性的设置，为 Pod &lt;a href=&#34;https://kubernetes.io/blog/2017/03/advanced-scheduling-in-kubernetes/&#34; target=&#34;_blank&#34;&gt;分配指定特性的节点&lt;/a&gt;。例如一个一个高 IO 的数据库 Pod 可能需要调度到配有 SSD 存储的节点上。还有可能为了降低延迟，将一系列的 Pod 调度到同一节点上，这一操作称为 Pod 亲和性。Kubernetes 还支持自定义调度器，完全由第三方实现分配逻辑。&lt;/p&gt;

&lt;p&gt;Kubernetes 调度器的最大亮点就是其简单性。前面描述的多数策略都能很轻松的实现。只要通过一点注解和标签，就能完成 Pod 和节点之间的亲和或排斥的定义。通过对 Pod 和节点的键值对设置就能够实现成熟的调度逻辑。&lt;/p&gt;

&lt;h2 id=&#34;超越-pod-和节点的-kubernetes-调度器&#34;&gt;超越 Pod 和节点的 Kubernetes 调度器&lt;/h2&gt;

&lt;p&gt;Kubernetes 可能是目前最好的资源调度器之一。兼顾简单和扩展能力的调度器让用户能够解决很多传统分布式系统中的调度问题。&lt;/p&gt;

&lt;p&gt;在高度分布的环境中，Kubernetes 正在成为首选的作业调度和管理工具。这些作业包括在物理服务器上部署虚拟机、在边缘设备上运行容器，甚至还具有将控制平面扩展到 Serverless 环境这样的其他调度器上的能力。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://kubevirt.io/&#34; target=&#34;_blank&#34;&gt;KuberVirt&lt;/a&gt; 就是一个 Kubernetes 上的虚拟机管理插件，它让用户能够像 Pod 一样在 Kubernetes 或 OpenShift 集群上运行虚拟机。这一系统用 &lt;a href=&#34;https://kubernetes.io/docs/concepts/api-extension/custom-resources/#customresourcedefinitions&#34; target=&#34;_blank&#34;&gt;CRD&lt;/a&gt; 的形式来进行虚拟机的设置，完成了对 Kubernetes 的扩展。KubeVirt 虚拟机运行在普通的 Kubernetes Pod 之内，从而具有了访问标准 Pod 网络和存储的能力，并且可以使用标准的 &lt;code&gt;kubectl&lt;/code&gt; 或者类似的 Kubernetes 工具来进行管理。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/d4c17aba-k8s-scheduler-1.png&#34; alt=&#34;KubeVirt&#34; /&gt;&lt;/p&gt;

&lt;p&gt;来自 Mirantis 的 &lt;a href=&#34;https://github.com/Mirantis/virtlet&#34; target=&#34;_blank&#34;&gt;Virtlet&lt;/a&gt; 项目让虚拟机可以在 Kubernetes 集群中像普通 Pod 一样运行。运维人员可以用 &lt;code&gt;kubectl&lt;/code&gt; 命令管理虚拟机，并且将虚拟机以一等公民的身份纳入集群网络。有了 Virtlet 就可以构建高级的 Kubernetes 对象，例如 Deployment、Statefulset 或者 DaemonSet。&lt;/p&gt;

&lt;p&gt;微软的 &lt;a href=&#34;https://github.com/virtual-kubelet/virtual-kubelet&#34; target=&#34;_blank&#34;&gt;Virtual Kubelet&lt;/a&gt; 是最有趣的一个调度器。Virtual Kubelet 是一个 Agent，运行在注册为 Kubernetes 集群节点的外部环境之中。这个 Agent 会通过 Kubernetes API 创建节点资源。通过对&lt;a href=&#34;https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/&#34; target=&#34;_blank&#34;&gt;污染和隔离&lt;/a&gt;功能的使用，会通过本地 API 来进行外部环境中的 Pod 调度。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/1cd893e9-k8s-scheduler-2.jpg&#34; alt=&#34;Virtual Kubelet&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Virtual Kubelet 可以在 &lt;a href=&#34;https://azure.microsoft.com/en-us/services/container-instances/&#34; target=&#34;_blank&#34;&gt;Azure Container Instance&lt;/a&gt;、&lt;a href=&#34;https://azure.microsoft.com/en-us/services/iot-edge/&#34; target=&#34;_blank&#34;&gt;Azure IoT Edge&lt;/a&gt; 以及 &lt;a href=&#34;https://aws.amazon.com/fargate/&#34; target=&#34;_blank&#34;&gt;AWS Fargate&lt;/a&gt; 上运行。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/25822176-k8s-scheduler-3.png&#34; alt=&#34;Virtual Kubelet on Azure Container &#34; /&gt;&lt;/p&gt;

&lt;p&gt;另外，我还写过其他文章，介绍了 Virtual Kubelet 的&lt;a href=&#34;https://thenewstack.io/lightning-fast-container-provisioning-with-microsofts-azure-container-instances/&#34; target=&#34;_blank&#34;&gt;架构&lt;/a&gt;和&lt;a href=&#34;https://thenewstack.io/explore-multicloud-deployments-aci-connector-kubernetes/&#34; target=&#34;_blank&#34;&gt;部署指南&lt;/a&gt;，可供读者参考。&lt;/p&gt;

&lt;h2 id=&#34;更进一步-自定义调度器和-crd&#34;&gt;更进一步——自定义调度器和 CRD&lt;/h2&gt;

&lt;p&gt;上面讨论的例子只是冰山一角。Kubernetes 正在成为当代基础设施的基础，正在迈入传统业务应用领域——例如 ERP 和 CRM。&lt;/p&gt;

&lt;p&gt;应用提供商们将会越来越重的依赖 Kubernetes 的两个功能：自定义调度器和 CRD。&lt;/p&gt;

&lt;p&gt;正如前文所说，Kubernetes 的自定义调度器让开发者可以实现自己的调度逻辑。Pod 的声明中就可以通知控制面跳过缺省调度器，转而采用自定义调度。这一机制能够保障集群内的 Pod 得到正确安置。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://portworx.com/&#34; target=&#34;_blank&#34;&gt;Portworx&lt;/a&gt; 是一个云原生生存储公司，它使用自定义调度器来创建 &lt;a href=&#34;https://portworx.com/stork-storage-orchestration-kubernetes/&#34; target=&#34;_blank&#34;&gt;STORK&lt;/a&gt;（Kubernetes 存储编排运行时：STorage Orchestrator Runtime for Kubernetes）。从而保障它的 Stateful Pod 只会在安装了 Portworx 驱动和存储的节点上运行。这一功能对于运行其上的数据库负载的可用性保障很有帮助。&lt;/p&gt;

&lt;p&gt;Kubernetes 中的 CRD 为自定义对象提供了简单且强大的生命周期支持。自定义资源是一种对象，对 Kubernetes API 进行了扩展，开发人员可以利用这一机制将自己的 API 引入 Kubernetes。CRD 文件声明了自定义对象的定义，让 API Server 能够处理整个生命周期。在 Kubernetes 中部署 CRD，就是让 Kubernetes API Server 开始支持某种自定义资源。&lt;/p&gt;

&lt;p&gt;CRD 创建之后，运维人员可以使用 &lt;code&gt;kubectl&lt;/code&gt; 或者第三方工具来进行管理，这一过程和 Pod 等内置对象并无二致。ISV 可以利用 CRD 的方式将自己的软件进行打包和部署。&lt;/p&gt;

&lt;p&gt;Kubernetes 的扩展性，使其具备了成为统一调度管理工具的潜质。&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>小技巧：外部域名挂接 Azure 中的 K8S 负载均衡</title>
      <link>/post/azure-lb/</link>
      <pubDate>Wed, 25 Jul 2018 09:13:35 +0800</pubDate>
      <guid>/post/azure-lb/</guid>
      <description>&lt;p&gt;使用 &lt;a href=&#34;https://github.com/Azure/acs-engine&#34; target=&#34;_blank&#34;&gt;ACS Engine&lt;/a&gt; 在 Azure 上安装好了 Kubernetes 之后，就可以使用 Loadbalancer 类型的服务了。测试条件下，一般会使用不同域名通过同一个 Ingress Controller 访问不同服务的方式。&lt;/p&gt;

&lt;p&gt;例如我们创建一个 Nginx Ingress Controller，服务启动之后会看到大概这样的信息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl describe svc ingress-nginx
Name:                     ingress-nginx
Namespace:                ingress-nginx
...
Type:                     LoadBalancer
IP:                       10.0.22.78
LoadBalancer Ingress:     104.216.145.198
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来可以使用 &lt;a href=&#34;https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest&#34; target=&#34;_blank&#34;&gt;az&lt;/a&gt; 命令查询我们 Azure 中已经生效的外部 IP 所对应的记录内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;az network public-ip list \
--query &amp;quot;[?ipAddress==&#39;104.216.145.198&#39;]&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;会得到一条 JSON 记录：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
&amp;quot;dnsSettings&amp;quot;: {
    &amp;quot;domainNameLabel&amp;quot;: &amp;quot;k8s-5b&amp;quot;,
    &amp;quot;fqdn&amp;quot;: &amp;quot;k8s-5b.cloudapp.azure.com&amp;quot;,
    &amp;quot;reverseFqdn&amp;quot;: null
},
...
&amp;quot;name&amp;quot;: &amp;quot;...&amp;quot;,
&amp;quot;idleTimeoutInMinutes&amp;quot;: 4,
&amp;quot;ipAddress&amp;quot;: &amp;quot;104.216.145.198&amp;quot;,
&amp;quot;ipConfiguration&amp;quot;: {
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来，我们可以给这个 Ip 设置一个 AZURE 的内部域名，同样使用 &lt;code&gt;az&lt;/code&gt; 客户端：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;az network public-ip update
--name=k8s-master-ip-k8s-5b55d212-28708154 \
--set dnsSettings.domainN
ameLabel=&#39;awesome-name-fcsaqz87d&#39; \
--resource-group=k8s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;命令执行后，会返回一长串的描述信息，跟上述的 List 结果类似，节选我们的变更部分：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;quot;dnsSettings&amp;quot;: {
    &amp;quot;domainNameLabel&amp;quot;: &amp;quot;awesome-name-fcsaqz87d&amp;quot;,
    &amp;quot;fqdn&amp;quot;: &amp;quot;awesome-name-fcsaqz87d.southeastasia.cloudapp.azure.com&amp;quot;,
    &amp;quot;reverseFqdn&amp;quot;: null
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里可以看到，这个外部 IP 有了一个域名，接下来，我们可以在我们的域名提供商控制台上，为域名设置 CNAME 记录，指到这个 &lt;code&gt;fqdn&lt;/code&gt; 上，然后可以用 nslookup 上进行验证，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;nslookup prom.abc.xyz
canonical name = awesome-name-fcsaqz87d.southeastasia.cloudapp.azure.com.
Name:   awesome-name-fcsaqz87d.southeastasia.cloudapp.azure.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;就可以用外部域名来访问我们 Kubernetes 上的 Ingress 资源了。&lt;/p&gt;

&lt;p&gt;更进一步的，可以将泛域名设置到这一 IP 上，同时使用 &lt;a href=&#34;https://letsencrypt.org/&#34; target=&#34;_blank&#34;&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; 的泛域名证书来完成对服务的 HTTPS 加固过程。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Metallb - 贫苦 K8S 用户的负载均衡支持</title>
      <link>/post/intro-metallb/</link>
      <pubDate>Tue, 24 Jul 2018 00:09:48 +0800</pubDate>
      <guid>/post/intro-metallb/</guid>
      <description>

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;在私有网络上运行 Kubernetes，和御三家相比，对 &lt;code&gt;LoadBalancer&lt;/code&gt; 类型的服务的支持应该是众多表面差异中最醒目的一个了。类型为 &lt;code&gt;LoadBalancer&lt;/code&gt; 的服务在 Kubernetes 中并没有直接支持，NodePort 和 ExternalIP 方案让很多私有云用户成为了 K8S 世界中的二等公民。接下来介绍的 &lt;a href=&#34;https://metallb.universe.tf/&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;Metallb&lt;/code&gt;&lt;/a&gt;，就给私有 Kubernetes 用户带来了一个方便、可用（而且不太成熟）的软件解决方案。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;该项目发布于 2017 年底，当前处于 Beta 阶段。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Metallb 会在 Kubernetes 内运行，监控服务对象的变化，一旦察觉有新的 &lt;code&gt;LoadBalancer&lt;/code&gt; 服务运行，并且没有可申请的负载均衡器之后，就会完成两部分的工作：&lt;/p&gt;

&lt;h3 id=&#34;地址分配&#34;&gt;地址分配&lt;/h3&gt;

&lt;p&gt;用户需要在配置中提供一个地址池，Metallb 将会在其中选取地址分配给服务。&lt;/p&gt;

&lt;h3 id=&#34;地址广播&#34;&gt;地址广播&lt;/h3&gt;

&lt;p&gt;根据不同配置，Metallb 会以二层（ARP/NDP）或者 BGP 的方式进行地址的广播。&lt;/p&gt;

&lt;h2 id=&#34;支持范围&#34;&gt;支持范围&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;不支持 IPVS&lt;/p&gt;
&lt;/blockquote&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&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;Calico&lt;/td&gt;
&lt;td&gt;部分支持（有附加文档）&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Flannel&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Kube-router&lt;/td&gt;
&lt;td&gt;不支持（正在跟进）&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Romana&lt;/td&gt;
&lt;td&gt;支持（有附加文档）&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Weave Net&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&#34;一个小测试&#34;&gt;一个小测试&lt;/h2&gt;

&lt;h3 id=&#34;安装&#34;&gt;安装&lt;/h3&gt;

&lt;p&gt;Metallb 支持 Helm 和 YAML 两种安装方法，这里我们使用第二种：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.7.1/manifests/metallb.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;很简单，Metallb 就会开始安装，会生成自己的命名空间以及 RBAC 配置。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get pods -n metallb-system
NAME                         READY     STATUS    RESTARTS   AGE
controller-b7896bf94-g449l   1/1       Running   0          51m
speaker-mt6kd                1/1       Running   0          51m
speaker-sqbn4                1/1       Running   0          51m
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;配置&#34;&gt;配置&lt;/h3&gt;

&lt;p&gt;接下来我们要生成一个 Configmap 文件，为 Metallb 设置网址范围以及协议相关的选择和配置，这里以一个简单的二层配置为例：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: my-ip-space
      protocol: layer2
      addresses:
      - 10.211.55.240/28
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：这里的 IP 地址范围需要跟集群实际情况相对应。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl apply&lt;/code&gt; 命令应用之后，使用 &lt;code&gt;kubectl logs -f [metallb-controller-pod]&lt;/code&gt; 会看到配置更新过程。&lt;/p&gt;

&lt;h3 id=&#34;测试&#34;&gt;测试&lt;/h3&gt;

&lt;p&gt;创建一个 Nginx 的服务，服务类型为 &lt;code&gt;LoadBalancer&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1
        ports:
        - name: http
          containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: LoadBalancer
&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 svc
NAME       TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)         AGE
kube-dns   ClusterIP      10.96.0.10      &amp;lt;none&amp;gt;          53/UDP,53/TCP   7d
nginx      LoadBalancer   10.96.245.212   10.211.55.240   80:32207/TCP    54m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里就发现，LoadBalancer 类型的服务，分配到了我们地址池中的第一个 IP。&lt;/p&gt;

&lt;p&gt;用 &lt;code&gt;curl http://10.211.55.240&lt;/code&gt; 验证，就会发现返回了 Nginx 的欢迎信息。&lt;/p&gt;

&lt;h2 id=&#34;补充&#34;&gt;补充&lt;/h2&gt;

&lt;p&gt;除了这里提到的一点点简单配置之外，Metallb 的配置能力还是比较强大的，这点可以参考官网，其中谈及了不少较为务实的案例，另外还提到了部分 Issue 供用户参考。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>实验手记：Kubernetes 中进行 PVC 的扩容</title>
      <link>/post/resizing-pvc/</link>
      <pubDate>Sun, 15 Jul 2018 00:11:28 +0800</pubDate>
      <guid>/post/resizing-pvc/</guid>
      <description>

&lt;p&gt;Kubernetes 1.11 版本中，PVC 的扩容功能进入了 Beta 阶段，一般来说，Kubernetes 功能进入 Beta 阶段就意味着基本可以用于生产了。这里就做几个小测试，看看这一功能的使用方法。&lt;/p&gt;

&lt;h2 id=&#34;开始之前&#34;&gt;开始之前&lt;/h2&gt;

&lt;p&gt;首先当然是要有一个 Kubernetes 1.11 版本的集群。并且提供了支持 Auto provision 的存储。下面的实验是基于 Azure 的 ACS-Engine 集群。&lt;/p&gt;

&lt;h3 id=&#34;创建-storageclass&#34;&gt;创建 StorageClass&lt;/h3&gt;

&lt;p&gt;接下来准备两个 Storage Class 对象，分别命名为 &lt;code&gt;common&lt;/code&gt; 和 &lt;code&gt;expend&lt;/code&gt;，二者主体基本一致，文件名分别为 &lt;code&gt;sc-common.yaml&lt;/code&gt; 以及 &lt;code&gt;sc-exp.yaml&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: exp  # sc-common.yaml 中这里的值为 common
parameters:
  cachingmode: None
  kind: Managed
  storageaccounttype: Standard_LRS
provisioner: kubernetes.io/azure-disk
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true # sc-common.yaml 中删掉这一行
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f common.yaml
storageclass.storage.k8s.io/common created
$ kubectl apply -f exp.yaml
storageclass.storage.k8s.io/exp created
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;创建一个-pvc&#34;&gt;创建一个 PVC&lt;/h3&gt;

&lt;p&gt;我们接下来创建一个 PVC，初始首先测试一下 &lt;code&gt;common&lt;/code&gt; 这个 Storageclass，后续的 PVC 操作都从这一个 YAML 中修改而来。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 2Gi
  storageClassName: common
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;同样使用 &lt;code&gt;kubectl&lt;/code&gt; 创建这个 PVC：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl apply -f pvc.yaml
persistentvolumeclaim/myclaim created
$ kubectl get pvc -w
NAME      STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
myclaim   Pending                                       common         9s
myclaim   Pending                                 common    10s
myclaim   Pending   pvc-e3f8e886-8776-11e8-b82d-000d3aa2ebc3   0                   common    11s
myclaim   Bound     pvc-e3f8e886-8776-11e8-b82d-000d3aa2ebc3   2Gi       RWO       common    11s
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;第一次扩容尝试&#34;&gt;第一次扩容尝试&lt;/h3&gt;

&lt;p&gt;PVC 进入 &lt;code&gt;Bound&lt;/code&gt; 状态之后，我们编辑 &lt;code&gt;pvc.yaml&lt;/code&gt;，将容量改成 3Gi，并重新 Apply：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cat pvc.yaml | sed  &amp;quot;s/2Gi/3Gi/&amp;quot; | kubectl apply -f -
Error from server (Forbidden): error when applying patch:
...
for: &amp;quot;STDIN&amp;quot;: persistentvolumeclaims &amp;quot;myclaim&amp;quot; is forbidden: only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;结果表明，这次扩容失败了，失败的原因是 Storageclass 不支持扩容&lt;/p&gt;

&lt;h3 id=&#34;使用新的-storageclass-创建-pvc&#34;&gt;使用新的 Storageclass 创建 PVC&lt;/h3&gt;

&lt;p&gt;接下来我们将这个 PVC 删除，使用 exp 这个 Storageclass 重建 PVC：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl delete -f pvc.yaml
persistentvolumeclaim &amp;quot;myclaim&amp;quot; deleted
$ sed -i .bak s/common/exp/ pvc.yaml
$ kubectl apply -f pvc.yaml
persistentvolumeclaim/myclaim created
$ kubectl get pvc -w
NAME      STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
myclaim   Bound     pvc-37eb6014-8778-11e8-b82d-000d3aa2ebc3   2Gi        RWO            exp            11s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建之后，我们可以再次尝试对 PVC 进行扩容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cat pvc.yaml | sed  &amp;quot;s/2Gi/1Gi/&amp;quot; | kubectl apply -f -
The PersistentVolumeClaim &amp;quot;myclaim&amp;quot; is invalid: spec.resources.requests.storage: Forbidden: field can not be less than previous value
$ cat pvc.yaml | sed  &amp;quot;s/2Gi/3Gi/&amp;quot; | kubectl apply -f -
persistentvolumeclaim/myclaim configured
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里两次执行命令：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;缩容是不允许的&lt;/li&gt;
&lt;li&gt;扩容成功&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接下来我们再次获取 PVC 信息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl describe pvc myclaim
Name:          myclaim
Namespace:     default
StorageClass:  exp
Status:        Bound
Volume:        pvc-37eb6014-8778-11e8-b82d-000d3aa2ebc3
...
Capacity:      2Gi
Access Modes:  RWO
Conditions:
...
FileSystemResizePending
...
Waiting for user to (re-)start a pod to finish file system resize of volume on node.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里会看到，虽然没出错，但是容量还是原有的 2G，他的 Condition 中提示，需要进行一个 Pod 绑定才能真正生效。&lt;/p&gt;

&lt;h3 id=&#34;绑定-pod&#34;&gt;绑定 Pod&lt;/h3&gt;

&lt;p&gt;新建一个 Deployment 来使用前面创建的 PVC：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sleep
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sleep
        version: v1
    spec:
      containers:
      - name: sleep
        image: dustise/sleep:v0.5
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: data
          mountPath: &amp;quot;/data&amp;quot;
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: myclaim
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次查看 PVC 的情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl describe pvc myclaim
...
Capacity:      3Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;容量的修改的确生效了。&lt;/p&gt;

&lt;h3 id=&#34;绑定之后的-pvc-扩容&#34;&gt;绑定之后的 PVC 扩容&lt;/h3&gt;

&lt;p&gt;再次对这个 PVC 进行扩容，我们这次从 3Gi 扩容到 4Gi：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cat pvc.yaml | sed  &amp;quot;s/2Gi/4Gi/&amp;quot; | kubectl apply -f -
persistentvolumeclaim/myclaim configured
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后获取一下 PVC 的情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl describe pvc myclaim
...
Capacity:      3Gi
...
Events:
  Warning  VolumeResizeFailed     31s (x2 over 56s)  volume_expand
  Original Error: failed request: autorest/azure: Service returned an error. Status=&amp;lt;nil&amp;gt; Code=&amp;quot;OperationNotAllowed&amp;quot; Message=&amp;quot;Cannot resize disk k8s-5b49c85f-dynamic-pvc-37eb6014-8778-11e8-b82d-000d3aa2ebc3 while it is attached to running VM /subscriptions/6d9be255-d214-4502-a51d-08e1d9c4a7fb/resourceGroups/k8s/providers/Microsoft.Compute/virtualMachines/k8s-agentpool1-17067717-0.&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这一情况看来，这次扩容仍然没有生效，错误信息中有提示，无法在已经成功挂载的卷上进行扩容，因此我们清除所有 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl scale deployment sleep --replicas 0
deployment.extensions/sleep scaled
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在相关 Pod 消失之后，我们可以再次 &lt;code&gt;describe pvc myclaim&lt;/code&gt;，发现这个 PVC 又一次处于等待绑定的状态中。使用 scale 指令恢复 Deployment 运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl scale deployment sleep --replicas 1
deployment.extensions/sleep scaled
$ kubectl describe pvc myclaim
...
Capacity:      4Gi
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;PVC 的扩容再次成功了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中对持久卷进行扩容</title>
      <link>/post/k8s-1.11-resizing-pvc/</link>
      <pubDate>Sun, 15 Jul 2018 00:08:31 +0800</pubDate>
      <guid>/post/k8s-1.11-resizing-pvc/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2018/07/12/resizing-persistent-volumes-using-kubernetes/&#34; target=&#34;_blank&#34;&gt;Resizing Persistent Volumes using Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://www.linkedin.com/in/gnufied&#34; target=&#34;_blank&#34;&gt;Hemant Kumar&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes v1.11 中，持久卷扩容能力升级为 Beta 阶段。这个功能让用户可以轻松的通过编辑 PVC 对象的方式修改现有卷的容量。没有这一功能之前，要对卷容量进行修改，需要要和存储后端进行手工交互，或者对 PV 以及 PVC 进行删除重建操作。持久卷不支持缩容操作。&lt;/p&gt;

&lt;p&gt;v1.8 中卷扩展功能就已经进入 Alpha 阶段，v1.11 之前需要在 feature gate 中开启 &lt;code&gt;ExpandPersistentVolumes&lt;/code&gt;，以及 admission 控制器 &lt;code&gt;PersistentVolumeClaimResize&lt;/code&gt;（防止在底层存储不支持扩容的情况下对 PVC 进行扩容）。在 Kubernetes v1.11 中，这两个项目都会被缺省激活。&lt;/p&gt;

&lt;p&gt;虽然功能已经被激活，但是集群管理员还是需要进行操作，让用户能够对自己的卷进行扩容。Kubernetes 1.11 内置了对部分卷插件的扩容支持：AWS-EBS、GCE-PD、Azure Disk、Azure File、Glusterfs、Cinder、Portworx、以及 Ceph RBD。管理员确定底层存储能够支持卷扩展之后，就可以在 &lt;code&gt;StorageClass&lt;/code&gt; 对象中设置 &lt;code&gt;allowVolumeExpansion&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt; 来启用这一功能了。只有从 &lt;code&gt;StorageClass&lt;/code&gt; 中创建的 PVC 才允许使用卷扩展：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
parameters:
  type: pd-standard
provisioner: kubernetes.io/gce-pd
allowVolumeExpansion: true
reclaimPolicy: Delete
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;任何从这一 &lt;code&gt;StorageClass&lt;/code&gt; 中创建的 PVC 都能够通过编辑的方式来申请更多空间。Kubernetes 会处理 Storage 字段的变更，据此申请空间，触发卷的扩容。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi #更新这一字段，修改 PVC 容量
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;文件系统扩展&#34;&gt;文件系统扩展&lt;/h2&gt;

&lt;p&gt;GCE-PD、AWS-EBS、Azure Disk、Cinder 以及 Ceph RBD 这些块存储卷通常需要首先进行文件系统的扩展，然后被扩展的卷的额外空间才能为 Pod 所用。引用这些卷的 Pod 重启时，Kubernetes 会自动完成这些任务。&lt;/p&gt;

&lt;p&gt;网络挂载的文件系统，例如 Glusterfs 和 Azure File，因为不需要进行文件系统扩展，因此可以在不重启 Pod 的情况下直接进行扩展。&lt;/p&gt;

&lt;p&gt;只有终止引用卷的 Pod，才会触发对应文件系统的扩展，更确切地说：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;编辑 PVC，申请更多空间。&lt;/li&gt;
&lt;li&gt;底层存储对底层卷进行了扩展之后，PV 对象就会响应这一变化，PVC 会进入 &lt;code&gt;FileSystemResizePending&lt;/code&gt; 状态。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以运行 &lt;code&gt;kubectl get pvc &amp;lt;pvc_name&amp;gt; -o yaml&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
  namespace: default
  uid: 02d4aa83-83cd-11e8-909d-42010af00004
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 14Gi
  storageClassName: standard
  volumeName: pvc-xxx
status:
  capacity:
    storage: 9G
  conditions:
  - lastProbeTime: null
    lastTransitionTime: 2018-07-11T14:51:10Z
    message: Waiting for user to (re-)start a pod to finish file system resize of
      volume on node.
    status: &amp;quot;True&amp;quot;
    type: FileSystemResizePending
  phase: Bound
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;PVC 进入 &lt;code&gt;FileSystemResizePending&lt;/code&gt; 状态，引用 PVC 的 Pod 就可以重新启动来结束文件系统在 Node 上的扩展过程了。可以通过删除和重建 Pod 的方式进行重启，也可以通过对 Deployment 的伸缩来完成这一过程。&lt;/li&gt;
&lt;li&gt;文件系统的扩展操作完成之后，PVC 会自动更新，设置为新的容量。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;文件系统扩展过程中遇到任何错误，都会在 Pod 中以 Event 的形式表现出来。&lt;/p&gt;

&lt;h2 id=&#34;在线文件系统扩展&#34;&gt;在线文件系统扩展&lt;/h2&gt;

&lt;p&gt;Kubernetes v1.11 还引入了一个 Alpha 功能，叫做在线文件系统扩展。这个功能可以对一个正在被 Pod 使用的卷进行文件系统的扩展。这个功能还处于 Alpha 阶段，因此需要通过 Feature gate 启用 &lt;code&gt;ExpandInUsePersistentVolumes&lt;/code&gt;。目前支持的有 GCE-PD、AWS-EBS、Cinder 以及 Ceph RBD。当激活这个功能后，引用被扩展的卷的 Pod 无需重启。文件系统会随着卷扩展的步骤进行扩展。文件系统的扩展只有在 Pod 引用被扩展的卷的时候才会发生，所以如果没有 Pod 引用这个卷，那么就不会进行文件系统扩展。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Draft vs Gitkube vs Helm vs Ksonnet vs Metaparticle vs Skaffold</title>
      <link>/post/draft-vs-gitkube-vs-helm-vs-ksonnet-vs-metaparticle-vs-skaffold/</link>
      <pubDate>Tue, 26 Jun 2018 11:29:18 +0800</pubDate>
      <guid>/post/draft-vs-gitkube-vs-helm-vs-ksonnet-vs-metaparticle-vs-skaffold/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://blog.hasura.io/draft-vs-gitkube-vs-helm-vs-ksonnet-vs-metaparticle-vs-skaffold-f5aa9561f948&#34; target=&#34;_blank&#34;&gt;Draft vs Gitkube vs Helm vs Ksonnet vs Metaparticle vs Skaffold&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://blog.hasura.io/@shahidh&#34; target=&#34;_blank&#34;&gt;Shahidh K Muhammed&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;tl-dr&#34;&gt;TL;DR&lt;/h2&gt;

&lt;h3 id=&#34;draft-https-draft-sh&#34;&gt;&lt;a href=&#34;https://draft.sh/&#34; target=&#34;_blank&#34;&gt;Draft&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;向 K8S 集群部署代码（自动“构建-推送-部署”）。&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&#34;https://github.com/Azure/draft/tree/master/packs&#34; target=&#34;_blank&#34;&gt;Draft 打包支持的语言&lt;/a&gt; 的代码可以不编写 Dockerfile 或者 K8S 元数据文件直接进行部署。&lt;/li&gt;
&lt;li&gt;需要 draft 以及 helm 客户端，集群要部署 tiller，本地 Docker，Docker 仓库。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;gitkube-https-gitkube-sh&#34;&gt;&lt;a href=&#34;https://gitkube.sh/&#34; target=&#34;_blank&#34;&gt;Gitkube&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;向 K8S 集群部署代码（自动“构建-推送-部署”）。&lt;/li&gt;
&lt;li&gt;Git 推送触发部署，本机无依赖。&lt;/li&gt;
&lt;li&gt;Git 仓库中需要提供 Dockerfile 以及 K8S 元数据文件，集群中需部署 gitkube。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;helm-https-helm-sh&#34;&gt;&lt;a href=&#34;https://helm.sh/&#34; target=&#34;_blank&#34;&gt;Helm&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;在 K8S 集群上对 Chart（其中包含一个应用的所有 K8S 资源定义文件）进行部署和管理。&lt;/li&gt;
&lt;li&gt;提供了很多通用应用（例如 MySQL、Mediawiki 等）的 Chart。&lt;/li&gt;
&lt;li&gt;客户端需要 Helm，服务端需要 Tiller，Chart 定义可以在本地也可以在仓库中保存。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;ksonnet-https-ksonnet-io&#34;&gt;&lt;a href=&#34;https://ksonnet.io/&#34; target=&#34;_blank&#34;&gt;Ksonnet&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;在 jsonnet 上定义 K8S 元数据文件，然后进行部署。&lt;/li&gt;
&lt;li&gt;可以对通用模式（例如 Deployment + Service）和应用栈（例如 Redis）进行复用。&lt;/li&gt;
&lt;li&gt;需要 jsonnet 知识，安装 ksonnet 客户端。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;metaparticle-https-metaparticle-io&#34;&gt;&lt;a href=&#34;https://metaparticle.io/&#34; target=&#34;_blank&#34;&gt;Metaparticle&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;使用 &lt;a href=&#34;https://metaparticle.io/&#34; target=&#34;_blank&#34;&gt;Metaparticle 支持的语言&lt;/a&gt; 编写代码，然后部署到 K8S 集群（自动“构建-推送-部署”）&lt;/li&gt;
&lt;li&gt;在应用的代码中直接定义容器化和 K8S 相关内容，傻瓜化的编写过程，无需编写 Dockerfile 或者 Yaml。&lt;/li&gt;
&lt;li&gt;需要本地 Docker 部署，需要相关语言的库。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;skaffold-https-github-com-googlecloudplatform-skaffold&#34;&gt;&lt;a href=&#34;https://github.com/GoogleCloudPlatform/skaffold&#34; target=&#34;_blank&#34;&gt;Skaffold&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;向 K8S 集群部署代码（自动“构建-推送-部署”）。&lt;/li&gt;
&lt;li&gt;监控源代码变更，变更发生后就会触发“构建-推送-部署”过程，Pipeline 可配置。&lt;/li&gt;
&lt;li&gt;需要 Skaffold 客户端、Dockerfile、K8S 元数据文件、Skaffold 元数据文件，本地 Docker 以及私库。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;下面进入一点细节。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;img/1*eBG8XpHztwns7GR9qtz0Pg.png&#34; alt=&#34;Brands&#34; /&gt;&lt;/p&gt;

&lt;p&gt;当今的 Kubernetes 炙手可热，用户们寻求更多的方式和流程来进行 Kubernetes 集群上的应用部署。&lt;code&gt;kubectl&lt;/code&gt; 已经成为底层工具，用户需要更易用的流程。Draft、Gitkube、Helm、Ksonnet、MetaParticle 以及 Skaffold 都是用来帮助开发人员在 Kubernetes 上进行应用构建和部署的工具。&lt;/p&gt;

&lt;p&gt;Draft、Gitkube 和 Skaffold 减轻了开发人员的负担，在构建应用的过程中，能够更快的在 Kubernetes 上运行起来。Helm 和 Ksonnet 提供了定义应用、更新版本、选择不同集群等功能，在应用构建完成，进入发布就绪状态之后，这两个工具可以提高部署能力。Metaparticle 是比较独特的一个，他把包含 yaml、dockerfile 这些东西集成到业务代码之中。&lt;/p&gt;

&lt;p&gt;所以用户自身的用例中如何进行选择？&lt;/p&gt;

&lt;h2 id=&#34;正文&#34;&gt;正文&lt;/h2&gt;

&lt;h3 id=&#34;draft-https-draft-sh-1&#34;&gt;&lt;a href=&#34;https://draft.sh/&#34; target=&#34;_blank&#34;&gt;Draft&lt;/a&gt;&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;在任何 Kubernetes 集群上简化应用的开发和部署。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;顾名思义，Draft 让面向 Kubernetes 的应用开发变得简单。官方宣称，对于运行在 Kubernetes 上的应用，Draft 这一工具是帮助开发过程而非部署的。Draft 文档中推荐使用 Helm 进行应用部署。&lt;/p&gt;

&lt;p&gt;他的目标是：开发人员还在开发调试之中的本地的代码，不经提交到版本控制系统，直接运行到 Kubernetes 集群上。开发人员对 Draft 发布的应用变更满意之后，才提交给版本控制系统。&lt;/p&gt;

&lt;p&gt;Draft 不是用来在生产环境上进行部署的，他的用意就是在于快速推进面向 Kubernetes 环境的开发过程。他内部使用 Helm 来进行变更，因此他和 Helm 的集成是非常紧密的。&lt;/p&gt;

&lt;h4 id=&#34;架构&#34;&gt;架构&lt;/h4&gt;

&lt;p&gt;&lt;img src=&#34;img/1*kV56ClDz_rrMg5wT4lpQ5Q.png&#34; alt=&#34;Draft architecture&#34; /&gt;&lt;/p&gt;

&lt;p&gt;如上图所示，&lt;code&gt;Draft&lt;/code&gt; 客户端是一个关键组件。它感知代码的变化，然后从 &lt;code&gt;Repo&lt;/code&gt; 中获取对应的 &lt;code&gt;Pack&lt;/code&gt;。&lt;code&gt;Pack&lt;/code&gt; 是一个 Dockerfile 和 Helm chart 的合体，他们一起定义了应用的运行环境。&lt;code&gt;Pack&lt;/code&gt; 定义之后保存在 &lt;code&gt;Repo&lt;/code&gt; 中。用户可以定义自己的 &lt;code&gt;Pack&lt;/code&gt; 和 &lt;code&gt;Repo&lt;/code&gt;，这两个对象可以保存在本地，也可以在 Git 仓库之中。&lt;/p&gt;

&lt;p&gt;只要有对应的 &lt;code&gt;Pack&lt;/code&gt;，任何一个包含源码的目录都可以进行部署。使用 &lt;code&gt;draft create&lt;/code&gt; 处理目录之后，会在目录中添加 Dockerfile、Helm chart 以及 draft.toml 文件，&lt;code&gt;draft up&lt;/code&gt; 能够构建 Docker 镜像，推送到私库，然后使用 Helm Chart 部署应用。每次代码变更之后，再次执行这一命令，就会产生一个新的部署。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;draft connect&lt;/code&gt; 命令能够进行端口转发，以此在本地获取容器的日志。他还能够和 &lt;code&gt;nginx-ingress&lt;/code&gt; 集成，为上面部署的应用提供域名。&lt;/p&gt;

&lt;h4 id=&#34;从-0-到-kubernetes&#34;&gt;从 0 到 Kubernetes&lt;/h4&gt;

&lt;p&gt;下面是一个用 Draft 把 Python 应用运行到 K8S 集群上的步骤。可以从&lt;a href=&#34;https://github.com/Azure/draft/blob/master/docs/getting-started.md&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;获得更详细的指导。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;先决条件&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 集群（包括 kubectl）&lt;/li&gt;
&lt;li&gt;Helm 客户端&lt;/li&gt;
&lt;li&gt;Draft 客户端&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Docker 镜像库&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ helm init
$ draft init
$ draft config set registry docker.io/myusername
$ git clone https://github.com/Azure/draft
$ cd draft/examples/example-python
$ draft create
$ draft up
## 代码修改
$ draft up
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;用例&#34;&gt;用例&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;开发运行在 Kubernetes 上的应用。&lt;/li&gt;
&lt;li&gt;用于在提交到版本控制之前的“内部流程”。&lt;/li&gt;
&lt;li&gt;预 CI：应用完成 Draft 过程之后，可以由 CI/CD 接管。&lt;/li&gt;
&lt;li&gt;不应该用在生产环境部署环节。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;gitkube-https-gitkube-sh-1&#34;&gt;&lt;a href=&#34;https://gitkube.sh/&#34; target=&#34;_blank&#34;&gt;Gitkube&lt;/a&gt;&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;使用 git push 构建 Docker 镜像并在 Kubernetes 上进行部署。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Gitkube 是一个用来构建 Docker 镜像并向 Kubernetes 上部署的工具，他的起点就是 &lt;code&gt;git push&lt;/code&gt;，不像 Draft，他不需要客户端，只需在集群上独立运行。&lt;/p&gt;

&lt;p&gt;任何带有 Dockerfile 的代码仓库，都可以使用 gitkube 进行部署。Gitkube 安装和部署在集群之上，开发人员可以获取一个包含 git URL 的 CRD。开发人员推送到仓库的代码，会触发集群一端的 Docker Build 以及 Kubectl 发布流程。可以使用 kubectl 或 helm 等类似工具给应用创建应用元数据。&lt;/p&gt;

&lt;p&gt;Gitkube 的重点是即插即用的安装过程，以及沿用既有的知名工具（git 以及 kubectl）。对需要部署的仓库没有什么假设。Docker build 的上下文以及 Dockerfile 所在路径，都可以进行配置。Git 连接认证是通过 SSH 公钥进行的。任何时候代码发生变更、提交和推送，都会触发后面的构建和部署过程。&lt;/p&gt;

&lt;h4 id=&#34;架构-1&#34;&gt;架构&lt;/h4&gt;

&lt;p&gt;&lt;img src=&#34;img/1*n9Il4vKaq9gHC5qQyF-9Tg.png&#34; alt=&#34;Gitkube Architecture&#34; /&gt;&lt;/p&gt;

&lt;p&gt;集群侧有三个组件，一个远程 CRD 可以定义针对一个远端 URL 发生 Push 的时候如何应对，&lt;code&gt;gitkubed&lt;/code&gt; 构建 Docker 镜像并更新部署，&lt;code&gt;gitkube-controller&lt;/code&gt; 会监控 CRD，随变化更新 &lt;code&gt;gitkubed&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;在集群上创建这些对象之后，开发者就可以使用 kubectl 来创建应用的定义了。创建一个 &lt;code&gt;remote&lt;/code&gt; 对象，告诉 gitkube，当 git push 发生时该做什么。Gitkube 把远程 url 写回到 &lt;code&gt;remote&lt;/code&gt; 对象的状态字段中。&lt;/p&gt;

&lt;h4 id=&#34;从-0-到-kubernetes-1&#34;&gt;从 0 到 Kubernetes&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;先决条件

&lt;ul&gt;
&lt;li&gt;Kubernetes 集群（包括 kubectl）。&lt;/li&gt;
&lt;li&gt;git。&lt;/li&gt;
&lt;li&gt;集群上安装好 gitkube （&lt;code&gt;kubectl create&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面是将应用提交到 Kubernetes 的步骤，也包含了 gitkube 的安装过程。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;已经过时&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ git clone https://github.com/hasura/gitkube-example
$ cd gitkube-example
$ kubectl create -f k8s.yaml
$ cat ~/.ssh/id_rsa.pub | awk &#39;$0=&amp;quot;  - &amp;quot;$0&#39; &amp;gt;&amp;gt; &amp;quot;remote.yaml&amp;quot;
$ kubectl create -f remote.yaml
$ kubectl get remote example -o json | jq -r &#39;.status.remoteUrl&#39;
$ git remote add example [remoteUrl]
$ git push example master
## 编辑代码
## 提交和推送
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;用例-1&#34;&gt;用例&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;使用 Git 进行简单的部署，无需 Docker Build。&lt;/li&gt;
&lt;li&gt;在 Kubernetes 上开发应用。&lt;/li&gt;
&lt;li&gt;开发过程中，WIP 分支可以多次提交，迅速反馈。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;helm-https-helm-sh-1&#34;&gt;&lt;a href=&#34;https://helm.sh/&#34; target=&#34;_blank&#34;&gt;Helm&lt;/a&gt;&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Kubernetes 的包管理系统。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Helm 使用一种称为 &lt;code&gt;Chart&lt;/code&gt; 的形式，来管理 Kubernetes 上的应用。Helm 为应用创建 YAML 并进行版本化操作，这样可以对包含 Deployment 在内的所有对象进行回滚。Chart 可以包含 Deployment、Service 以及 Configmap 等。Chart 的模板允许用户方便的修改部署细节，另外还支持带有依赖关系的复杂应用。&lt;/p&gt;

&lt;p&gt;Helm 的主要目标是在生产环境中部署和管理应用程序。对比 Draft 和 Gitkube，Helm 不是用来开发的，而是用来部署的。另外现在有大量的预构建 &lt;code&gt;Chart&lt;/code&gt; 可以供 Helm 使用。&lt;/p&gt;

&lt;h4 id=&#34;架构-2&#34;&gt;架构&lt;/h4&gt;

&lt;p&gt;&lt;img src=&#34;img/1*Nsme583Ut1TY6IDZjKl27w.png&#34; alt=&#34;Helm architecture&#34; /&gt;&lt;/p&gt;

&lt;p&gt;首先看看 Chart。我们之前说过，Chart 之中包含一系列的信息，这些信息是部署应用到 Kubernetes 中的必要条件。其中可能包含 Deployment、Service、Configmap、Secret 以及 Ingress 等。所有的定义都是以 Yaml 文件模板的形式出现，另外还包含嵌套的依赖 Chart。Chart 可以在 Chart 仓库中发布。&lt;/p&gt;

&lt;p&gt;Helm 有两个主要组件，分别是 Helm 客户端和 Tiller 服务器。客户端用于管理 Chart 和仓库，并且和 Tiller 服务器进行通信，来完成对 Chart 的部署和管理。&lt;/p&gt;

&lt;p&gt;Tiller 组件运行在集群上，和 Kubernetes API 服务器打交道，进行对象的实际操作。&lt;/p&gt;

&lt;p&gt;Helm 不处理源码，用户需要使用 CI/CD 系统来构建镜像，然后用 Helm 来部署合适的镜像。&lt;/p&gt;

&lt;h4 id=&#34;从-0-到-kubernetes-2&#34;&gt;从 0 到 Kubernetes&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;先决条件

&lt;ul&gt;
&lt;li&gt;Kubernetes 集群&lt;/li&gt;
&lt;li&gt;Helm 客户端&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接下来是一个在 Kubernetes 集群上使用 Helm 部署 Wordpress 博客的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ helm init
$ helm repo update
$ helm install stable/wordpress
## 更新版本
$ helm upgrade [release-name] [chart-name]
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;用例-2&#34;&gt;用例&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;打包：包含多个 Kubernetes 对象的复杂应用可以集中在一起。&lt;/li&gt;
&lt;li&gt;可复用的 Chart 仓库。&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;/li&gt;
&lt;li&gt;只能部署已经构建完成的镜像。&lt;/li&gt;
&lt;li&gt;具备生命周期管理能力，可以管理多个 Kubernetes 对象的升级和回滚。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;ksonnet-https-ksonnet-io-1&#34;&gt;&lt;a href=&#34;https://ksonnet.io/&#34; target=&#34;_blank&#34;&gt;Ksonnet&lt;/a&gt;&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;一个支持客户按操作的框架，提供可扩展的 Kubernetes 配置。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ksonnet 是为 Kubernetes 定义应用配置的另一种方法。它并没有使用 Kubernetes 世界中常用的 YAML 语言，改用一种称为 Jsonnet 的 JSON 模板语言。Ksonnet 客户端最终会渲染出 YAML 文件并提交给集群。&lt;/p&gt;

&lt;p&gt;这一系统的主要功能就是定义可复用的组件，并利用该工具渐进式的进行程序构建。&lt;/p&gt;

&lt;h4 id=&#34;架构-3&#34;&gt;架构&lt;/h4&gt;

&lt;p&gt;&lt;img src=&#34;img/1*mvkdIs0QOGp8xoTMOacM9g.png&#34; alt=&#34;Ksonnet 架构&#34; /&gt;&lt;/p&gt;

&lt;p&gt;基础的构建单位被称为 &lt;code&gt;part&lt;/code&gt;，&lt;code&gt;part&lt;/code&gt; 可以协作构成 &lt;code&gt;prototype&lt;/code&gt;。一个 &lt;code&gt;prototype&lt;/code&gt; 配合参数之后，就成为了一个 &lt;code&gt;component&lt;/code&gt;，&lt;code&gt;component&lt;/code&gt; 可以聚合在一起，成为一个 &lt;code&gt;application&lt;/code&gt;。&lt;code&gt;application&lt;/code&gt; 可以部署到多个 &lt;code&gt;environment&lt;/code&gt; 之中。&lt;/p&gt;

&lt;p&gt;最基础的流程就是使用 &lt;code&gt;ks init&lt;/code&gt; 命令创建一个应用目录，使用 &lt;code&gt;ks generate&lt;/code&gt; 生成（或者也可以自行编写）&lt;code&gt;component&lt;/code&gt; 的元数据文件，使用 &lt;code&gt;ks apply &amp;lt;env&amp;gt;&lt;/code&gt; 命令可以把应用部署到集群/环境之中。可以用 &lt;code&gt;ks env&lt;/code&gt; 命令来管理不同的环境。&lt;/p&gt;

&lt;p&gt;简而言之，Ksonnet 帮助用户定义和管理应用，他把应用视作一系列使用 Jsonnet 的组件进行管理，并部署在不同的 Kubernetes 集群上。&lt;/p&gt;

&lt;p&gt;跟 Helm 类似，Ksonnet 不和源码发生关系，他是一个使用 Jsonnet 为 Kubernetes 定义应用的工具。&lt;/p&gt;

&lt;h4 id=&#34;从-0-到-kubernetes-3&#34;&gt;从 0 到 Kubernetes&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;先决条件

&lt;ul&gt;
&lt;li&gt;Kubernetes 集群&lt;/li&gt;
&lt;li&gt;ksonnet 客户端&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接下来是一个留言板例子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ ks init
$ ks generate deployed-service guestbook-ui \
     --image gcr.io/heptio-images/ks-guestbook-demo:0.1 \
     --type ClusterIP
$ ks apply default
## 变更
$ ks apply default
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;用例-3&#34;&gt;用例&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;使用 Jsonnet 编写配置很有弹性。&lt;/li&gt;
&lt;li&gt;打包：复杂配置可以用匹配组件的方式集成起来。&lt;/li&gt;
&lt;li&gt;可复用的组件和原型库：避免重复。&lt;/li&gt;
&lt;li&gt;方便的多环境部署。&lt;/li&gt;
&lt;li&gt;CD 的最后一步。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;metaparticle-https-metaparticle-io-1&#34;&gt;&lt;a href=&#34;https://metaparticle.io/&#34; target=&#34;_blank&#34;&gt;Metaparticle&lt;/a&gt;&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;为容器和 Kubernetes 而生的云原生标准库。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Metaparticle 将自己定位于云原生应用的标准库，他内置了经过验证的分布式系统模式，而开发人员可以用习惯的编程语言通过原语的方式方便的采用这些先进模式。&lt;/p&gt;

&lt;p&gt;他提供了简易的语言接口，帮助用户构建可以容器化并部署到 Kubernetes 上的应用，这些应用会直接兼容负载均衡等基础设施。无需自行编写 Dockerfile 或者 Kubernetes 元数据文件，所有相关内容都在代码中的用原语来体现。&lt;/p&gt;

&lt;p&gt;例如一个 Python Web 应用，可以给 main 函数加入一个叫做 &lt;code&gt;containerize&lt;/code&gt; 的 Decorator（从 metaparticle 中 import）。当执行这段 Python 代码的时候，会构建 Docker 镜像并部署到 Decorator 参数中提到的 Kubernetes 集群上。缺省集群定义来自 kubectl 上下文。所以切换环境就和切换当前上下文是等价的。&lt;/p&gt;

&lt;p&gt;在 NodeJS、Java 以及 .NET 上也提供了类似的原语。另外还正在开发更多的语言支持。&lt;/p&gt;

&lt;h4 id=&#34;架构-4&#34;&gt;架构&lt;/h4&gt;

&lt;p&gt;各种语言的 &lt;code&gt;metaparticle&lt;/code&gt; 库都包含所需的原语，绑定了构建 Docker 镜像、推送到私库、创建 Kubenretes yaml 文件并在集群上部署的代码。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/metaparticle-io/package&#34; target=&#34;_blank&#34;&gt;Metaparticle 包&lt;/a&gt;中内置了各种语言用来构建容器的支持。而 &lt;a href=&#34;https://github.com/metaparticle-io/sync&#34; target=&#34;_blank&#34;&gt;Metaparticle Sync&lt;/a&gt; 则包含了在不同机器上运行的不同容器进行同步的能力。&lt;/p&gt;

&lt;p&gt;目前支持的语言包括：JavaScript/NodeJS、Python、Java 以及 .NET。&lt;/p&gt;

&lt;h4 id=&#34;从-0-到-kubernetes-4&#34;&gt;从 0 到 Kubernetes&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;先决条件

&lt;ul&gt;
&lt;li&gt;Kubernetes 集群。&lt;/li&gt;
&lt;li&gt;特定语言的 Metaparticle 库。&lt;/li&gt;
&lt;li&gt;Docker。&lt;/li&gt;
&lt;li&gt;Docker 私库。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一个只包含相关内容的 Python 例子，可以使用这些代码构建 Docker 镜像，并在 Kubernetes 上进行部署。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;@containerize(
    &#39;docker.io/your-docker-user-goes-here&#39;,
    options={
        &#39;ports&#39;: [8080],
        &#39;replicas&#39;: 4,
        &#39;runner&#39;: &#39;metaparticle&#39;,
        &#39;name&#39;: &#39;my-image&#39;,
        &#39;publish&#39;: True
    })
def main():
    Handler = MyHandler
    httpd = SocketServer.TCPServer((&amp;quot;&amp;quot;, port), Handler)
    httpd.serve_forever()
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;用例-4&#34;&gt;用例&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;只想开发应用，不想担心 Kubernetes YAML 或者 Dockerfile。&lt;/li&gt;
&lt;li&gt;不想掌握多种工具和文件格式，又想搭上容器和 Kubernetes 快车的开发人员。&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;h3 id=&#34;skaffold-https-github-com-googlecloudplatform-skaffold-1&#34;&gt;&lt;a href=&#34;https://github.com/GoogleCloudPlatform/skaffold&#34; target=&#34;_blank&#34;&gt;Skaffold&lt;/a&gt;&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;简单可重复的 Kubernetes 开发。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Skaffold 能够处理构建镜像、推送镜像以及在 Kubernetes 上进行部署。跟 Gitkube 类似，任何包含 Dockerfile 的目录都可以用 Skaffold 部署到 kubernetes 集群上。&lt;/p&gt;

&lt;p&gt;Skaffold 会在本地构建 Docker 镜像，推送到私库，然后使用 &lt;code&gt;skaffold&lt;/code&gt; 客户端进行部署。他还会监测目录，如此一来，目录中的代码一旦发生变化，就会触发重新构建和部署。这个过程还会从容器中获取日志。&lt;/p&gt;

&lt;p&gt;可以使用 YAML 文件来构建、推送、部署的 Pipeline，所以开发者可以混合使用合适的工具，例如 Docker build 和 Google Container Builder，Kubectl 和 Helm 等。&lt;/p&gt;

&lt;h4 id=&#34;架构-5&#34;&gt;架构&lt;/h4&gt;

&lt;p&gt;&lt;img src=&#34;img/1*elRwOUeoOJvGOK9JdHZIrA.png&#34; alt=&#34;Skaffold 概览&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Skaffold 客户端做了所有的工作。他会查找一个叫做 &lt;code&gt;skaffold.yaml&lt;/code&gt; 的文件，其中包含了必须完成的任务。一个典型的例子就是在 &lt;code&gt;skaffold dev&lt;/code&gt; 运行的目录中查找 Dockerfile 构建 Docker 镜像，并使用 sha256 进行标记，推送镜像，把镜像设置到 Kubernetes 元数据文件之中，最后发布到集群上。这一系列动作会被目录中的变更所触发。来自部署容器的日志会出现在同一个 Watch 窗口中。&lt;/p&gt;

&lt;p&gt;Skaffold 和 Draft 和 Gitkube 很像，但是更具弹性，如上图所示，他能管理不同的“构建-推送-部署”流程。&lt;/p&gt;

&lt;h4 id=&#34;从-0-到-kubernetes-5&#34;&gt;从 0 到 Kubernetes&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;先决条件

&lt;ul&gt;
&lt;li&gt;Kubernetes 集群&lt;/li&gt;
&lt;li&gt;Skaffold 客户端&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Docker 镜像库&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面的步骤，部署一个 Go 编写的 Hello World 应用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ git clone https://github.com/GoogleCloudPlatform/skaffold
$ cd examples/getting-started
## 编辑 skaffold.yaml，加入 Docker 仓库
$ skaffold dev
## 打开新终端: 编辑代码
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;用例-5&#34;&gt;用例&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;方便部署。&lt;/li&gt;
&lt;li&gt;迭代构建——持续的构建-发布流程。&lt;/li&gt;
&lt;li&gt;为 Kubernetes 开发应用。&lt;/li&gt;
&lt;li&gt;在 CICD 流程中定义“构建-推送-部署”流程。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Containerd 1.1.0 尝鲜记</title>
      <link>/post/containerd-kubeadm/</link>
      <pubDate>Wed, 30 May 2018 00:41:44 +0800</pubDate>
      <guid>/post/containerd-kubeadm/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://blog.fleeto.us/post/kubernetes-containerd-integration-goes-ga/&#34; target=&#34;_blank&#34;&gt;Containerd 1.1.0 的 Kubernetes 支持已经进入可用阶段&lt;/a&gt;，Kubernetes 1.10 和未来的的 Docker 版本都会以此为基础，作为一个熟练软件安装工，自然是要先睹为快了。&lt;/p&gt;

&lt;p&gt;这里使用 Kubeadm 进行测试。&lt;/p&gt;

&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;

&lt;p&gt;首先进行 Kubeadm 的环境准备：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;安装 libseccomp, conntrack&lt;/li&gt;
&lt;li&gt;关闭防火墙服务&lt;/li&gt;
&lt;li&gt;开启 sysctl：&lt;code&gt;ip_forward&lt;/code&gt;、&lt;code&gt;net.bridge.bridge-nf-call-iptables&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;参考&lt;a href=&#34;https://kubernetes.io/docs/tasks/tools/install-kubeadm/&#34; target=&#34;_blank&#34;&gt;官方指南&lt;/a&gt;，安装 kubeadm、kubelet 以及 kubectl，此处暂时不启动 kubelet 服务。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;安装-contaierd&#34;&gt;安装 contaierd&lt;/h2&gt;

&lt;p&gt;下载 &lt;a href=&#34;https://storage.googleapis.com/cri-containerd-release/cri-containerd-1.1.0.linux-amd64.tar.gz&#34; target=&#34;_blank&#34;&gt;cri-containerd 1.1.0&lt;/a&gt;，并解压，其中包含 &lt;code&gt;/usr&lt;/code&gt;、&lt;code&gt;/etc&lt;/code&gt; 以及 &lt;code&gt;opt&lt;/code&gt; 三个目录，这里我们只是用前两个目录的内容，目录结构如下，直接复制即可：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;├── etc
│   ├── crictl.yaml
│   └── systemd
│       └── system
│           └── containerd.service
└── usr
    └── local
        ├── bin
        │   ├── containerd
        │   ├── containerd-release
        │   ├── containerd-shim
        │   ├── containerd-stress
        │   ├── crictl
        │   ├── critest
        │   └── ctr
        └── sbin
            └── runc
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;crictl.yaml&lt;/code&gt;：crictl 的配置文件，缺省包含一行 &lt;code&gt;runtime-endpoint: unix:///run/containerd/containerd.sock&lt;/code&gt;，指定运行时的连接方式。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;containerd.service&lt;/code&gt;：服务文件，设置自动启动即可。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctr&lt;/code&gt;：containerd 客户端&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crictl&lt;/code&gt;：cri 客户端&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runc&lt;/code&gt;：运行时，contaienrd 依赖项&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里可以发现，并没有包含 containerd 自己的配置文件，可以使用 &lt;code&gt;containerd config default &amp;gt; /etc/containerd/config.toml&lt;/code&gt; 命令，来生成缺省配置文件，然后自行变更。例如可以&lt;a href=&#34;https://github.com/containerd/cri/blob/v1.0.0/docs/registry.md&#34; target=&#34;_blank&#34;&gt;修改仓库镜像地址&lt;/a&gt;。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;另外对国内用户比较重要的一点是，仍然是可以使用环境变量方式的配置来设置 &lt;code&gt;HTTP_PROXY&lt;/code&gt; 以及 &lt;code&gt;NO_PROXY&lt;/code&gt; 的内容。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;配置完成后，使用 &lt;code&gt;systemctl&lt;/code&gt; 启动服务。&lt;/p&gt;

&lt;h2 id=&#34;载入镜像&#34;&gt;载入镜像&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;docker.io/coredns/coredns:1.0.6&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-proxy-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/etcd-amd64&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-apiserver-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-controller-manager-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-proxy-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/kube-scheduler-amd64:v1.10.3&lt;/li&gt;
&lt;li&gt;k8s.gcr.io/pause:3.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;ctr 的镜像载入命令&lt;/strong&gt;：&lt;code&gt;ctr cri load image.tar&lt;/code&gt;，似乎不支持 gz。&lt;/p&gt;

&lt;h2 id=&#34;配置-kubelet-使用-containerd&#34;&gt;配置 Kubelet 使用 containerd&lt;/h2&gt;

&lt;p&gt;简单的在 Kubelet 的环境变量上加入如下内容，再启动 Kubelet 服务：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[Service]
Environment=&amp;quot;KUBELET_EXTRA_ARGS=--runtime-cgroups=/system.slice/containerd.service --container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;kubeadm-集群安装&#34;&gt;Kubeadm 集群安装&lt;/h2&gt;

&lt;p&gt;这里提供一个简单的初始化命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;kubeadm init \
--pod-network-cidr=192.168.0.0/16 \
--feature-gates CoreDNS=true \
--ignore-preflight-errors=Service-Docker \
--ignore-preflight-errors=SystemVerification \
--kubernetes-version=v1.10.3 # 防止 kubeadm 向服务器查询镜像列表。
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubeadm 缺省情况下依旧是需要检查 Docker 的运行情况的，因此这里我们使用 &lt;code&gt;--ignore-preflight-errors&lt;/code&gt; 开关关闭这项检查。&lt;/p&gt;

&lt;p&gt;Master 初始化结束之后，就可以跟随 kubeadm 指示，进入其他节点，运行 &lt;code&gt;kubeadm join&lt;/code&gt; 命令来加入集群了，加入命令同样需要设置 &lt;code&gt;--ignore-preflight-errors=all&lt;/code&gt; 来规避 Docker 检查。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;接下来可以按照自己喜好安装网络插件了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以使用 &lt;code&gt;kubectl describe nodes [node name]&lt;/code&gt; 来检查节点信息：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
Container Runtime Version:  containerd://1.1.0
Kubelet Version:            v1.10.3
Kube-Proxy Version:         v1.10.3
PodCIDR:                     192.168.0.0/24
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里可以看到，运行时已经更新为 &lt;code&gt;containerd://1.1.0&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&#34;后记&#34;&gt;后记&lt;/h2&gt;

&lt;p&gt;正如在前面文章提到的，containerd 并非 Docker 的替代品，只是一个子集，独立使用是很困难的，因此还是比较适合用于 Kubelet 控制之下的容器运行支持。&lt;/p&gt;

&lt;h2 id=&#34;下载链接以及参考链接&#34;&gt;下载链接以及参考链接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cri-containerd 1.1.0&lt;/strong&gt;：&lt;code&gt;https://storage.googleapis.com/cri-containerd-release/cri-containerd-1.1.0.linux-amd64.tar.gz&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kubeadm 安装指南&lt;/strong&gt;：&lt;code&gt;https://kubernetes.io/docs/tasks/tools/install-kubeadm/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;containerd 安装指南&lt;/strong&gt;：&lt;code&gt;https://github.com/containerd/containerd/releases&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Containerd 1.1.0 的 Kubernetes 支持已经进入可用阶段&lt;/strong&gt;： &lt;code&gt;https://blog.fleeto.us/post/kubernetes-containerd-integration-goes-ga/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes Containerd 集成进入 GA 阶段</title>
      <link>/post/kubernetes-containerd-integration-goes-ga/</link>
      <pubDate>Fri, 25 May 2018 02:09:22 +0800</pubDate>
      <guid>/post/kubernetes-containerd-integration-goes-ga/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2018/05/24/kubernetes-containerd-integration-goes-ga/&#34; target=&#34;_blank&#34;&gt;Kubernetes Containerd Integration Goes GA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.linkedin.com/in/liu-lantao-96a97351&#34; target=&#34;_blank&#34;&gt;Lantao Liu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.twitter.com/mikebrow&#34; target=&#34;_blank&#34;&gt;Mike Brown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在之前的博客（&lt;a href=&#34;https://kubernetes.io/blog/2017/11/containerd-container-runtime-options-kubernetes&#34; target=&#34;_blank&#34;&gt;Containerd Brings More Container Runtime Options for Kubernetes&lt;/a&gt;）中，我们介绍了 Kubernetes Containerd 集成的 Alpha 版本。经过六个月的开发，Containerd 的集成现在进入了 GA 阶段，现在可以将 &lt;a href=&#34;https://github.com/containerd/containerd/releases/tag/v1.1.0&#34; target=&#34;_blank&#34;&gt;Containerd 1.1&lt;/a&gt; 作为容器运行时为生产环境的 Kubernetes 提供支撑了。&lt;/p&gt;

&lt;p&gt;Containerd 1.1 支持 Kubernetes 1.10 及以上版本，支持 Kubernetes 的所有特性。目前在 Kubernetes 的测试设施中，Containerd 在 &lt;a href=&#34;https://cloud.google.com/&#34; target=&#34;_blank&#34;&gt;Google 云平台&lt;/a&gt;上的测试覆盖已经和 Docker 集成持平了。（参见：&lt;a href=&#34;https://k8s-testgrid.appspot.com/sig-node-containerd&#34; target=&#34;_blank&#34;&gt;Test Dashboard&lt;/a&gt;）。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;很高兴看到 Containerd 快速成长到今天的这一重要里程碑。阿里云从 Containerd 诞生之初就开始积极的采用 Containerd，开发团队对于简单和健壮的重视，使其完美的运行在我们的无服务器 Kubernetes 产品之中，提供了很好的性能和稳定性。Containerd 无疑将会成为容器世界的核心引擎，并持续创新前行。&lt;/p&gt;

&lt;p&gt;Xinwei，阿里云工程师。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;架构提升&#34;&gt;架构提升&lt;/h2&gt;

&lt;p&gt;Kubernetes 的 Containerd 集成架构有两次重大改进，每一次都让整个体系更加稳定和高效。&lt;/p&gt;

&lt;p&gt;Containerd 1.0 - CRI-Containerd（已终止）&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/cri-containerd.png&#34; alt=&#34;cri-containerd.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Containerd 1.0 中，需要一个叫做 cri-containerd 的守护进程，他的功能是提供 Kubelet 和 Containerd 之间的互操作支持。Cri-Containerd 处理来自 Kubelet 的 &lt;a href=&#34;https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/&#34; target=&#34;_blank&#34;&gt;容器运行时接口（CRI）&lt;/a&gt;服务请求，并使用 containerd 来管理容器和容器的镜像。对比之前的 Docker CRI 实现（&lt;a href=&#34;https://github.com/kubernetes/kubernetes/tree/v1.10.2/pkg/kubelet/Dockershim&#34; target=&#34;_blank&#34;&gt;Dockershim&lt;/a&gt;），他清理了整个体系中的一些多余部分。&lt;/p&gt;

&lt;p&gt;然而 Cri-containerd 和 Containerd 1.0 还是两个不同的守护进程，相互之间使用 gRPC 进行通信。额外进程给用户的理解和部署都造成了麻烦，并引入了不必要的通信开支。&lt;/p&gt;

&lt;p&gt;Containerd 1.1 - CRI 插件（目前）&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/containerd.png&#34; alt=&#34;containerd.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在 Containerd 1.1 中，Cri-containerd 守护进程进行了重构，成为了 Containerd 的 CRI 插件。CRI 插件处于 Containerd 1.1 内部，缺省启用。和 Cri-containerd 不同，CRI 插件和 Containerd 之间通过直接的程序调用来协同工作。新架构让这一产品更加稳定高效，去除了过程中的 gRPC 开销。用户现在可以直接使用 Containerd 1.1 来支撑 Kubernetes，不再需要 Cri-containerd 守护进程。&lt;/p&gt;

&lt;h2 id=&#34;性能&#34;&gt;性能&lt;/h2&gt;

&lt;p&gt;Containerd 1.1 的一个主要目标就是提高性能。这里的性能主要指的是 Pod 启动延迟以及守护进程的资源使用情况。&lt;/p&gt;

&lt;p&gt;下面的结果是 Containerd 1.1 和 Docker 18.03 CE 之间的对比。Containerd 1.1 集成使用了内置其中的 CRI 插件；Docker 18.03 CE 集成使用的是 Dockershim。&lt;/p&gt;

&lt;p&gt;下面的结果是使用 Kubernetes 节点性能 Benchmark 生成的，这个 Benchmark 工具是 &lt;a href=&#34;https://github.com/kubernetes/community/blob/master/contributors/devel/e2e-node-tests.md&#34; target=&#34;_blank&#34;&gt;Kubernetes 节点端到端测试&lt;/a&gt;的一部分。绝大多数的 Containerd 测试结果都是可以在 &lt;a href=&#34;http://node-perf-dash.k8s.io/&#34; target=&#34;_blank&#34;&gt;节点性能 Dashboard&lt;/a&gt; 上进行公开访问的。&lt;/p&gt;

&lt;h3 id=&#34;pod-启动延迟&#34;&gt;Pod 启动延迟&lt;/h3&gt;

&lt;p&gt;“105 pod batch startup benchmark” 结果显示，相对 Docker 18.03 CE 的 dochershim 集成来说，Containerd 1.1 的集成的延迟时间更短（越低越好）。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/latency.png&#34; alt=&#34;latency&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;cpu-和内存&#34;&gt;CPU 和内存&lt;/h2&gt;

&lt;p&gt;在 105 个 Pod 的稳定状态下，Containerd 1.1 集成消耗的 CPU 和内存都比 Docker 18.03 CE 的 Dockershim 集成要少。这个结果和节点上运行的 Pod 数量关系紧密，之所以选择 105 这个数字，是因为这是目前每节点上运行 Pod 的缺省数量上限。&lt;/p&gt;

&lt;p&gt;如下图所示，对比 Docker 18.03 CE 的 Dockershim 集成，Containerd 1.1 集成的 Kubelet CPU 占用降低了 30.89%，容器运行时 CPU 消耗降低了 68.13%，Kubelet 实际使用内存（RSS）降低了 11.30%，容器运行时 RSS 降低了 12.78%。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/cpu.png&#34; alt=&#34;cpu&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/memory.png&#34; alt=&#34;memory&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;crictl&#34;&gt;crictl&lt;/h2&gt;

&lt;p&gt;容器运行时命令行接口（CLI）对系统和应用的排错来说是个有用的工具。如果用 Docker 作为 Kubernetes 的容器运行时，系统管理员有时候需要登录到 Kubernetes 节点上去运行 Docker 命令，以便收集系统和应用的信息。例如使用 &lt;code&gt;docker ps&lt;/code&gt; 和 &lt;code&gt;docker inspect&lt;/code&gt; 检查应用的进程情况，&lt;code&gt;docker images&lt;/code&gt; 列出节点上的镜像，或者 &lt;code&gt;docker info&lt;/code&gt; 来检查容器运行时的配置等。&lt;/p&gt;

&lt;p&gt;对 Containerd 和所有其他的 CRI 兼容的容器运行时，尤其是 Dockershim 来说，我们推荐使用 &lt;code&gt;crictl&lt;/code&gt; 作为 Docker CRI 的继任者，用于 Kubernetes 节点上 pod、容器以及镜像的除错工具。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;crictl&lt;/code&gt; 在 Kubernetes 节点除错方面，提供了类似 Docker CLI 的使用体验， 并且 &lt;code&gt;crictl&lt;/code&gt; 能够支持所有 CRI 兼容的容器运行时。这一项目存放于 &lt;a href=&#34;https://github.com/kubernetes-incubator/cri-tools&#34; target=&#34;_blank&#34;&gt;kubernetes-incubator/cri-tools&lt;/a&gt;，目前版本是 &lt;a href=&#34;https://github.com/kubernetes-incubator/cri-tools/releases/tag/v1.0.0-beta.1&#34; target=&#34;_blank&#34;&gt;v1.0.0-beta.1&lt;/a&gt;。&lt;code&gt;crictl&lt;/code&gt; 的设计目的是理顺 Docker CLI 的功能，为用户提供更好的过渡体验，但是和 Docker CLI 又不尽相同。下面讲讲两者之间的一些重要区别。&lt;/p&gt;

&lt;h3 id=&#34;适用范围-crictl-是一个排错工具&#34;&gt;适用范围：crictl 是一个排错工具&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;crictl&lt;/code&gt; 的设计目的是排错，并非 Docker 或者 kubectl 的替代品。Docker 的 CLI 提供了大量的命令，使之成为重要的开发工具，但是在 Kubernetes 节点排错方面，就不尽人意了。有些 Docker 命令在 Kubernetes 上没什么用，例如 &lt;code&gt;docker network&lt;/code&gt; 和 &lt;code&gt;docker build&lt;/code&gt;；有些甚至会损害系统，比如说 &lt;code&gt;docker rename&lt;/code&gt;，&lt;code&gt;crictl&lt;/code&gt; 提供了刚好够用的命令来进行节点方面的除错工作，对于生产节点来说，明显会有更好的安全性。&lt;/p&gt;

&lt;h3 id=&#34;kubernetes-特性&#34;&gt;Kubernetes 特性&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;crictl&lt;/code&gt; 提供了一个对 Kubernetes 来说更加友好的容器视角。Docker CLI 并不了解 Kubernetes 的概念，例如 &lt;code&gt;pod&lt;/code&gt; 和 &lt;a href=&#34;https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;namespace&lt;/code&gt;&lt;/a&gt;，所以他无法提供容器和 Pod 的清晰视图。一个例子就是 &lt;code&gt;docker ps&lt;/code&gt; 的混乱输出：过长的 Docker 容器名称、Pause 容器和应用容器混杂在一起：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/docker-ps.png&#34; alt=&#34;docker ps&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.ianlewis.org/en/almighty-pause-container&#34; target=&#34;_blank&#34;&gt;Pause 容器&lt;/a&gt;是一个 Pod 的实现手段，每个 Pod 都会有一个 Pause 容器，所以列出 Pod 中包含的容器的时候，没必要把 Pause 容器显示出来。&lt;/p&gt;

&lt;p&gt;而 &lt;code&gt;crictl&lt;/code&gt; 是为 Kubernetes 设计的，他有不同的一组命令来和 Pod 以及容器进行交互。例如 &lt;code&gt;crictl pods&lt;/code&gt; 会列出 Pod 信息，而 &lt;code&gt;crictl ps&lt;/code&gt; 只会列出应用容器的信息。所有的信息都以表格形式进行展示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/crictl-pods.png&#34; alt=&#34;!crictl-pods.png&#34; /&gt;&lt;/p&gt;

&lt;p&gt;关于 &lt;code&gt;crictl&lt;/code&gt; 在 containerd 方面的细节，可以参看：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/containerd/cri/blob/master/docs/crictl.md&#34; target=&#34;_blank&#34;&gt;文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://asciinema.org/a/179047&#34; target=&#34;_blank&#34;&gt;演示视频&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;docker-怎么办&#34;&gt;Docker 怎么办？&lt;/h2&gt;

&lt;p&gt;“切换到 Containerd 是不是说我不能再用 Docker Engine 了？”我们经常听到这个问题，简单的答案就是：NO。&lt;/p&gt;

&lt;p&gt;Docker Engine 是在 Containerd 之上构建的。下个版本的 &lt;a href=&#34;https://www.docker.com/community-edition&#34; target=&#34;_blank&#34;&gt;Docker CE&lt;/a&gt; 就会使用 Containerd 1.1。当然，也就会有内置的缺省激活的 CRI 插件。这样一来，用户可以选择继续使用 Docker Engine 来做一些 Docker 的事情，也可以配置 Kubernetes 来使用其中的 Containerd，同时 Containerd 还会同时给同一节点上的 Docker Engine 提供支撑。下面的架构图就描述了 Docker Engine 和 Kubelet 共用 Containerd 的情况：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/docker-ce.png&#34; alt=&#34;docker ce&#34; /&gt;&lt;/p&gt;

&lt;p&gt;既然 Containerd 同时能够给 Kubelet 和 Docker Engine 提供支持，选择了使用 Containerd 集成的用户，得到的不仅仅是新的 Kubernetes 特性、性能和稳定性的增强，他们还会得到保留 Docker Engine 以便用于其他用例的选择。&lt;/p&gt;

&lt;p&gt;Containerd 的&lt;a href=&#34;https://github.com/containerd/containerd/blob/master/docs/namespaces.md&#34; target=&#34;_blank&#34;&gt;命名空间&lt;/a&gt;机制，让 Kubelet 和 Docker Engine 之间无法互相访问对方的容器和镜像。这样就保证了他们无法互相影响，这样的后果：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;docker ps&lt;/code&gt; 命令无法看到 Kubernetes 创建的容器；而应该使用 &lt;code&gt;crictl ps&lt;/code&gt;。反之亦然，用 &lt;code&gt;crictl ps&lt;/code&gt; 也是无法看到 Docker CLI 创建的容器。&lt;code&gt;crictl create&lt;/code&gt; 以及 &lt;code&gt;crictl runp&lt;/code&gt; 命令只用于出错。不推荐在生产节点上手动使用 &lt;code&gt;crictl&lt;/code&gt; 启动 Pod 或者容器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker images&lt;/code&gt; 不会看到 Kubernetes 拉回的镜像。同样需要使用 &lt;code&gt;crictl images&lt;/code&gt; 命令。反过来用 &lt;code&gt;docker pull&lt;/code&gt;、&lt;code&gt;docker load&lt;/code&gt; 或者 &lt;code&gt;docker build&lt;/code&gt; 生成的镜像，Kubernetes 也是无法看到的。可以使用 &lt;code&gt;crictl pull&lt;/code&gt; 命令来替代，可以使用 &lt;code&gt;[ctr](https://github.com/containerd/containerd/blob/master/docs/man/ctr.1.md) cri load&lt;/code&gt; 来载入镜像。&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Containerd 1.1 天然支持 CRI，可以直接给 Kubernetes 使用。&lt;/li&gt;
&lt;li&gt;Containerd 1.1 满足生产要求。&lt;/li&gt;
&lt;li&gt;Containerd 1.1 在 Pod 启动延迟和系统资源占用方面具有良好的性能表现。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crictl&lt;/code&gt; 是用于和 Containerd 1.1 以及其他 cri 兼容的容器运行时进行操作和节点除错的 CLI 工具。&lt;/li&gt;
&lt;li&gt;下一个 Docker CE 版本会包含 Containerd 1.1。用户有选择继续使用 Docker 来满足 Kubernetes 之外的容器需求，同时让 Kubernetes 使用来自 Docker 的同样的底层容器运行时。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里要感谢来自 Google、IBM、Docker、ZTE、ZJU 以及很多其他的个人，让这一产品发展至今。&lt;/p&gt;

&lt;p&gt;可以阅读 &lt;a href=&#34;https://github.com/containerd/containerd/releases/tag/v1.1.0&#34; target=&#34;_blank&#34;&gt;Release Notes&lt;/a&gt;，来了解 Containerd 1.1 详细的变更情况。&lt;/p&gt;

&lt;h2 id=&#34;尝鲜&#34;&gt;尝鲜&lt;/h2&gt;

&lt;p&gt;要使用 Containerd 作为 Kubernetes 的容器运行时来搭建集群：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/containerd/cri/blob/v1.0.0/docs/kube-up.md&#34; target=&#34;_blank&#34;&gt;在 GCE 上用 kube-up.sh 来启动一个生产级别的集群&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/containerd/cri/blob/v1.0.0/contrib/ansible/README.md&#34; target=&#34;_blank&#34;&gt;使用 Ansible 和 Kubeadm 搭建多节点集群&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;在 Google 云从头搭建集群，可以看看 &lt;a href=&#34;https://github.com/kelseyhightower/kubernetes-the-hard-way&#34; target=&#34;_blank&#34;&gt;Kubernetes the Hard Way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/containerd/cri/blob/v1.0.0/docs/installation.md&#34; target=&#34;_blank&#34;&gt;从发布压缩包开始自定义安装&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/linuxkit/linuxkit/tree/master/projects/kubernetes&#34; target=&#34;_blank&#34;&gt;使用 LinuxKit 在本地虚拟机上安装&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;贡献&#34;&gt;贡献&lt;/h2&gt;

&lt;p&gt;Container CRI 插件是一个开源的 Github 项目，在 Containerd 之内：&lt;a href=&#34;https://github.com/containerd/cri&#34; target=&#34;_blank&#34;&gt;https://github.com/containerd/cri&lt;/a&gt;。我们欢迎任何建议、问题、代码方面的贡献。&lt;a href=&#34;https://github.com/containerd/cri#getting-started-for-developers&#34; target=&#34;_blank&#34;&gt;开发者起步指南&lt;/a&gt;提供了如何成为贡献者方面的入门知识。&lt;/p&gt;

&lt;h2 id=&#34;社区&#34;&gt;社区&lt;/h2&gt;

&lt;p&gt;这个项目的开发和维护是由 Kubernetes SIG-Node 社区和 Containerd 社区联合负责的。我们希望能听到用户的反馈，要加入集群：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes/community/tree/master/sig-node&#34; target=&#34;_blank&#34;&gt;SIG-Node 社区网站&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Slack：

&lt;ul&gt;
&lt;li&gt;#sig-node，&lt;a href=&#34;http://kubernetes.slack.com&#34; target=&#34;_blank&#34;&gt;kubernetes.slack.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;#containerd, &lt;a href=&#34;https://dockr.ly/community&#34; target=&#34;_blank&#34;&gt;https://dockr.ly/community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;邮件列表：&lt;a href=&#34;https://groups.google.com/forum/#!forum/kubernetes-sig-node&#34; target=&#34;_blank&#34;&gt;https://groups.google.com/forum/#!forum/kubernetes-sig-node&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>在 K8S 集群中分布构建 Docker 镜像</title>
      <link>/post/jenkins-build-image-on-k8s-tricks/</link>
      <pubDate>Fri, 18 May 2018 18:48:12 +0800</pubDate>
      <guid>/post/jenkins-build-image-on-k8s-tricks/</guid>
      <description>

&lt;h2 id=&#34;镜像更新&#34;&gt;镜像更新&lt;/h2&gt;

&lt;p&gt;最近给我的 &lt;a href=&#34;https://github.com/fleeto/docker-jenkins&#34; target=&#34;_blank&#34;&gt;Jenkins 镜像&lt;/a&gt; 做了一些更新：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;加入了基于 RunC 的镜像操作工具：&lt;a href=&#34;https://github.com/genuinetools/img&#34; target=&#34;_blank&#34;&gt;IMG&lt;/a&gt;。在特权模式下，可以方便的使用命令行创建和操作 Docker 镜像。&lt;/li&gt;
&lt;li&gt;更新 Jenkins 以及 Remoting 版本。&lt;/li&gt;
&lt;li&gt;修复一些初始化问题。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面根据这一版本的镜像简单说说分布式构建过程中的一些要点。&lt;/p&gt;

&lt;h2 id=&#34;jenkins-镜像的一些值得注意的东西&#34;&gt;Jenkins 镜像的一些值得注意的东西&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Jenkins 在启动 Slave 节点时，会设置下面三个环境变量，所以在 &lt;code&gt;run.sh&lt;/code&gt; 就可以引用这几个环境变量启动 Worker 节点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JENKINS_URL：MASTER 服务的地址。&lt;/li&gt;
&lt;li&gt;JENKINS_NAME：为 Worker 节点分配的名称&lt;/li&gt;

&lt;li&gt;&lt;p&gt;JENKINS_SECRET：Worker 节点和 Master 节点通信时所使用的认证密钥。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;java \
-cp /usr/share/jenkins/slave.jar hudson.remoting.jnlp.Main \
-headless -url $JENKINS_URL $JENKINS_SECRET $JENKINS_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;img&lt;/code&gt; 工具依赖于 &lt;a href=&#34;https://github.com/opencontainers/runc/&#34; target=&#34;_blank&#34;&gt;runc&lt;/a&gt;，因此也需要把 runc 加入到镜像之中。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;config.xml&lt;/code&gt; 中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;直接设置了 8081 作为 jnlp 端口，因此在 Dockerfile 中也进行了 EXPOSE 声明。&lt;/li&gt;
&lt;li&gt;开启 CSRF 选项，并设置代理服务器兼容，防止在 Kube-Proxy 的情况下无法提供服务。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;jenkins-在-kubernetes-中的启动&#34;&gt;Jenkins 在 Kubernetes 中的启动：&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;RBAC：因为要启动 Worker 节点，因此在开启了 RBAC 的集群上，要给 Jenkins 的 Service Account 进行授权，允许进行 Pod 的操作。&lt;/li&gt;
&lt;li&gt;Service：需要暴露 jnlp 端口到集群内部，无需为其提供 loadbalancer 以及 nodePort 等形式的外网接入能力。&lt;/li&gt;
&lt;li&gt;可以根据需要提供加载卷，用于保存配置等。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;分布式构建的配置&#34;&gt;分布式构建的配置&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;首先需要安装 Kubernetes 插件。&lt;/li&gt;
&lt;li&gt;插件安装后，就可以在 Jenkins 的 credentials 中加入 &lt;code&gt;Kubernetes ServiceAccount&lt;/code&gt; 类型的凭据，用于连接和操作 Kubernetes 集群。&lt;/li&gt;
&lt;li&gt;容器模板设置中，系统缺省会提供一个名为 jnlp 的容器模板，要想使用自己的镜像，需要进行同名覆盖。

&lt;ul&gt;
&lt;li&gt;容器模板中可以按需设置加载卷等，例如要进行镜像的 PUSH 操作，就需要将 CA 证书通过一定方法加入到容器的信任列表之中。&lt;/li&gt;
&lt;li&gt;如果要进行 IMG 的操作，需要选中容器的 &lt;code&gt;Run in privileged mode&lt;/code&gt; 选项，开启特权模式。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;任务的设置&#34;&gt;任务的设置&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;要进行镜像的构建，就非常的容易，加入几行简单的 Shell 命令，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir tmp
# 生成一个 Dokerfile
echo &amp;quot;FROM registry.docker-cn.com/library/alpine&amp;quot; &amp;gt; tmp/Dockerfile
# 构建镜像
img build -t 10.211.55.19:5000/alpine:163 tmp
# 更新证书信任列表
update-ca-certificates
# 把镜像推送到私库
img push 10.211.55.19:5000/alpine:163
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>来自骷髅岛的 Ingress Controller：Kong</title>
      <link>/post/intro-kong/</link>
      <pubDate>Fri, 11 May 2018 12:26:22 +0800</pubDate>
      <guid>/post/intro-kong/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://konghq.com/&#34; target=&#34;_blank&#34;&gt;Kong&lt;/a&gt;，是一个在 Nginx 反向代理基础上发展而来的 API 网关产品。我之前一直在推动的 Service Mesh，主要关注的是集群（Mesh）内微服务之间的关系，而 API 网关所管理的则是微服务集群边缘，对外服务的管理。（据我观测，Istio 近期的文档已经出现了 Gateway 等说法，似乎也对这方面的问题颇有兴趣的样子）。&lt;/p&gt;

&lt;p&gt;传统的 API：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/comp-tra.png&#34; alt=&#34;Traditional api&#34; /&gt;&lt;/p&gt;

&lt;p&gt;API Gateway：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/comp-kong.png&#34; alt=&#34;API Gateway&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://konghq.com/blog/kubernetes-ingress-controller-for-kong/&#34; target=&#34;_blank&#34;&gt;5 月 8 日，Kong 发布了 Ingress Controller&lt;/a&gt;，对 Kubernetes 和对 Kong 自身来说都是个有意思的事情。&lt;/p&gt;

&lt;p&gt;首先，Ingress Controller 本来应该负责集群的对外通信，有些 Ingress Controller，例如 haproxy 和 Traefik 已经初步具备了这方面的能力。
其次，Kong 之前使用 API 调用的方式来进行管理，在 Ingress Controller 的上下文中，改用 CRD 方式进行管理，对于我等 YAML 程序员来说，无疑是个大大的利好消息。&lt;/p&gt;

&lt;p&gt;Kong 使用&lt;a href=&#34;https://konghq.com/plugins/&#34; target=&#34;_blank&#34;&gt;插件&lt;/a&gt;的方式提供了一些常见功能，这些现在也可以用 CRD 方式进行使用，其中包括日志、限流、认证、鉴权几大类。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/intro-illustration.png&#34; alt=&#34;plugins&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kong 同时提供商业和社区两个版本，目前有部分插件也是商业版独占的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;今天这一篇，就会对 Kong Ingress Controller 从部署到应用的介绍。&lt;/p&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;p&gt;官方提供了一个简易的 Kubernetes 环境中的&lt;a href=&#34;https://raw.githubusercontent.com/Kong/kubernetes-ingress-controller/master/deploy/single/all-in-one-postgres.yaml&#34; target=&#34;_blank&#34;&gt;安装文件&lt;/a&gt;；另外在 Ingress Controller 出现之前，Kong 也有一个相对更丰富的 &lt;a href=&#34;https://getkong.org/install/kubernetes/&#34; target=&#34;_blank&#34;&gt;Kubernetes 下的安装文档&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;全部组件都运行在 kong 命名空间。&lt;/p&gt;

&lt;h3 id=&#34;数据库&#34;&gt;数据库&lt;/h3&gt;

&lt;p&gt;安装过程中会创建一个 Postgres 的 StatefulSet，前面提到，这一版本对 Kubernetes 集群的最低版本要求是 1.8，如果是 1.8 版本，需要将这一个 StatefulSet API 版本改为 &lt;code&gt;apps/v1beta2&lt;/code&gt;。另外这一部分需要用 PVC 的形式给数据提供存储空间，所以集群中应该设置缺省 StorageClass。完整的 Kubernetes 安装文档中，还介绍了 Cassandra 的存储方式。&lt;/p&gt;

&lt;h3 id=&#34;crd&#34;&gt;CRD&lt;/h3&gt;

&lt;p&gt;安装过程中创建了如下的自定义资源：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;凭据：用于身份认证。&lt;/li&gt;
&lt;li&gt;服务消费者：给不同的 API 用户提供不同的消费者身份，以便实施不同的治理方式。&lt;/li&gt;
&lt;li&gt;插件：将过去使用 HTTP API 管理的插件系统，以 CRD 的形式在 Kubernetes 环境下统一管理。&lt;/li&gt;
&lt;li&gt;KongIngress：对反向代理行为本身进行定义。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;服务&#34;&gt;服务&lt;/h3&gt;

&lt;p&gt;其中提供了两组服务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kong-ingress-controller：暴露 8001 端口，用于对 Kong 进行管理。这里建议将服务类型改为 ClusterIP，而不是直接暴露于公网。&lt;/li&gt;
&lt;li&gt;kong-proxy：Ingress 服务，对其承载的接口调用都从此经过，可以根据集群情况酌情使用 NodePort 或者 ClusterIP -&amp;gt; Ingress 的方式对外提供服务。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;提供服务&#34;&gt;提供服务&lt;/h2&gt;

&lt;p&gt;安装结束后，就可以使用网关来对外提供服务了。&lt;/p&gt;

&lt;p&gt;官方提供了一个简单的&lt;a href=&#34;https://raw.githubusercontent.com/Kong/kubernetes-ingress-controller/master/deploy/manifests/dummy-application.yaml&#34; target=&#34;_blank&#34;&gt;例子应用&lt;/a&gt;，我们当然也可以选择别的应用来试用。为他编写一个 Ingress 资源：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: dummy
  annotations:
    kubernetes.io/ingress.class: nginx # 这里仍然是 nginx
spec:
  rules:
  - host: dummy.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: http-svc
          servicePort: 89
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;如果没有公网 Loadbalancer 条件，可以使用 &lt;code&gt;/etc/hosts&lt;/code&gt;、dnsmasq、或者 curl host 几种方式来模拟。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ingress 资源创建成功之后，就可以使用域名来访问这一服务了。&lt;/p&gt;

&lt;h3 id=&#34;tls-加密&#34;&gt;TLS 加密&lt;/h3&gt;

&lt;p&gt;作为 Ingress Controller，添加证书提供 https 服务也是基本要求之一。这方面 Kong Ingress Controller 使用的是 tls secret 的方式：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先获取证书，可以自行签名，或者使用已有证书文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl create secret tls rocks --key privkey.pem --cert fullchain.pem&lt;/code&gt;：创建一个名为 &lt;code&gt;rocks&lt;/code&gt; 的 Secret，其中包含我们的证书和私钥。&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在 Ingress 资源定义中加入下列内容，引用刚才创建的 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;tls:
- hosts:
- dummy.example.com
secretName: rocks
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这样就可以使用 https 进行访问了。&lt;/p&gt;

&lt;h2 id=&#34;试用限流插件&#34;&gt;试用限流插件&lt;/h2&gt;

&lt;p&gt;前面提到，Kong Ingress Controller 使用 CRD 方式来实现插件的应用。下面我们创建一个限流插件的 CRD：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: one-per-second-ten-per-hour
config:
  hour: 10
  limit_by: ip
  second: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;目前并没有很完善的插件 CRD 规范的文档，因此其中的具体字段需要去该插件的文档页面去查找，例如这里引用的 &lt;a href=&#34;https://getkong.org/plugins/rate-limiting/&#34; target=&#34;_blank&#34;&gt;Ratelimit&lt;/a&gt;。在这一个 YAML 中我们会发现，其中并没有表明具体使用的插件以及对应的服务，通过对 controller 管理端口的 &lt;code&gt;/plugins&lt;/code&gt; 进行查询，也会看到其中并没有定义活动的插件。&lt;/p&gt;

&lt;p&gt;要把它应用到具体服务上，还需要修改我们要控制的 Ingress 资源，在其中加入注解，来引用这一 CRD：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kubernetes.io/ingress.class: nginx
rate-limiting.plugin.konghq.com: one-per-second-ten-per-hour
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交新的 Ingress 之后，再次访问管理端口的 &lt;code&gt;plugins&lt;/code&gt; 路径，会得到以下响应：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;total&amp;quot;: 1,
    &amp;quot;data&amp;quot;: [
        {
            &amp;quot;created_at&amp;quot;: 1525966801000,
            &amp;quot;config&amp;quot;: {
                &amp;quot;redis_database&amp;quot;: 0,
                &amp;quot;policy&amp;quot;: &amp;quot;cluster&amp;quot;,
                &amp;quot;redis_timeout&amp;quot;: 2000,
                &amp;quot;hide_client_headers&amp;quot;: false,
                &amp;quot;hour&amp;quot;: 20,
                &amp;quot;limit_by&amp;quot;: &amp;quot;ip&amp;quot;,
                &amp;quot;redis_port&amp;quot;: 6379,
                &amp;quot;second&amp;quot;: 10,
                &amp;quot;fault_tolerant&amp;quot;: true
            },
            &amp;quot;id&amp;quot;: &amp;quot;8539eb6f-5467-11e8-a92e-000d3a07d45d&amp;quot;,
            &amp;quot;name&amp;quot;: &amp;quot;rate-limiting&amp;quot;,
            &amp;quot;enabled&amp;quot;: true,
            &amp;quot;route_id&amp;quot;: &amp;quot;f2961715-11fd-410a-a934-dbd6822e5fac&amp;quot;
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以使用 siege 或者 curl/wrk 等其他工具来访问 API，会发现超过限度之后，服务器返回 429 的状态码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;HTTP/1.1 200     0.20 secs:     727 bytes ==&amp;gt; GET  /
HTTP/1.1 200     0.22 secs:     729 bytes ==&amp;gt; GET  /
HTTP/1.1 429     0.20 secs:      38 bytes ==&amp;gt; GET  /
HTTP/1.1 429     0.21 secs:      38 bytes ==&amp;gt; GET  /
HTTP/1.1 429     0.20 secs:      38 bytes ==&amp;gt; GET  /
HTTP/1.1 200     0.20 secs:     729 bytes ==&amp;gt; GET  /
HTTP/1.1 200     0.20 secs:     727 bytes ==&amp;gt; GET  /
HTTP/1.1 429     0.20 secs:      38 bytes ==&amp;gt; GET  /
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;证明限流功能已经生效。&lt;/p&gt;

&lt;h3 id=&#34;试用消费者&#34;&gt;试用消费者&lt;/h3&gt;

&lt;p&gt;前面提到，可以使用消费者这一概念，对微服务的用户身份加以甄别，从而提供不同的管控方式。在前面的基础上，我们希望为部分用户修改一下响应内容。&lt;/p&gt;

&lt;p&gt;首先创建一个 KongConsumer 对象：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
  name: rich
username: boss
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来，为这个用户创建凭据，&lt;strong&gt;凭据是需要认证的，所以还要启用一个插件：key-auth&lt;/strong&gt;，官方文档中并没有提及这一点：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: configuration.konghq.com/v1
kind: KongCredential
metadata:
  name: rich-login
consumerRef: rich # 如果删除这一字段，就代表面向所有消费者。
type: key-auth
config:
  key: 62eb165c070a41d5c1b58d9d3d725ca1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后，为这个用户创建一个插件配置&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: rich-response
consumerRef: rich
config:
  hour: 100
  limit_by: ip
  second: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后，在 Ingress 资源中启用&lt;strong&gt;两个插件&lt;/strong&gt;，分别是 &lt;code&gt;key-auth&lt;/code&gt; 和 &lt;code&gt;response-transformer&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;response-transformer.plugin.konghq.com: boss
key-auth.plugin.konghq.com: auth
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;重新配置 Ingress 之后，可以使用 curl 进行校验：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;curl --header &amp;quot;apikey: aasome_key_data&amp;quot; -s -i https://dummy.example.com
HTTP/1.1 403 Forbidden
Date: Fri, 11 May 2018 04:17:57 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.13.1

{&amp;quot;message&amp;quot;:&amp;quot;Invalid authentication credentials&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到上面的认证没能通过。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;curl --header &amp;quot;apikey: some_key_data&amp;quot; -s -i https://dummy.example.com | grep boss
boss:  true
    x-consumer-username=boss2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Key Auth 认证插件根据 APIKey 取得了用户名，并且激活了 Response Transformer 插件，在 Header 中加入了我们配置的内容。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kong 原有的 API 在这里还是可以使用的，例如：
1. &lt;code&gt;curl http://[api-url]/plugins&lt;/code&gt; 查询生效插件
2. &lt;code&gt;curl http://[api-url]&lt;/code&gt; 返回 JSON 中的 &lt;code&gt;/plugins/available_on_server&lt;/code&gt; 列出所有可用插件。
3. &lt;code&gt;curl ttp://[api-url]/consumers&lt;/code&gt; 列出所有消费者。&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;https://konghq.com/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://raw.githubusercontent.com/Kong/kubernetes-ingress-controller/master/deploy/single/all-in-one-postgres.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://raw.githubusercontent.com/Kong/kubernetes-ingress-controller/master/deploy/manifests/dummy-application.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中用 Sidecar 为应用添加 Oauth 功能</title>
      <link>/post/sidecar-oauth-for-kubernetes-apps/</link>
      <pubDate>Mon, 09 Apr 2018 18:27:46 +0800</pubDate>
      <guid>/post/sidecar-oauth-for-kubernetes-apps/</guid>
      <description>

&lt;p&gt;Kubernetes 的 Pod 中可以同时运行共享网络栈的多个容器，使得 Sidecar 这种服务协作方式更加易于实施。这里我们就使用 Sidecar 方式，将 Keycloak 集成到 httpbin 服务上去，为没有认证的 httpbin 服务添加认证功能。&lt;/p&gt;

&lt;h2 id=&#34;概要&#34;&gt;概要&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://www.keycloak.org/&#34; target=&#34;_blank&#34;&gt;Keycloak（链接 1）&lt;/a&gt; 是 JBoss 的一个认证服务软件，使用代理的方式，为其他应用提供认证能力，除了本文说到的 Oauth 之外，还提供二次认证、LDAP 等丰富的相关功能。&lt;/p&gt;

&lt;p&gt;在 Kubernetes 上部署 Keycloak 服务，对其进行初始化，建立用户和认证系统，然后将 Keycloak-Proxy 和 Httpbin 集成在同一个 Pod 中进行部署运行，测试集成效果。&lt;/p&gt;

&lt;p&gt;集成后的访问路径如图所示：
&lt;img src=&#34;images/keycloak-flow.jpg&#34; alt=&#34;Keycloak flow&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;keycloak-服务的初始化&#34;&gt;Keycloak 服务的初始化&lt;/h2&gt;

&lt;p&gt;Keycloak 支持多种数据库存储，这里为了方便，就直接使用内置的 H2 数据库了。这里选用的镜像是&lt;code&gt;jboss/keycloak:3.4.2.Final&lt;/code&gt;，开放服务端口 8080，并设置了三个环境变量：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;KEYCLOAK_USER&lt;/code&gt;：KeyCloak 初始管理员账号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KEYCLOAK_PASSWORD&lt;/code&gt;：KeyCloak 初始管理员密码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PROXY_ADDRESS_FORWARDING&lt;/code&gt;：KeyCloak 部署在反向代理之后（Kubernetes 部署方式就在此列），就必须设置此变量为&lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接下来部署相关的 Service，如果有必要，还需要部署相关的 Ingress。&lt;/p&gt;

&lt;h2 id=&#34;设置-keycloak-服务器&#34;&gt;设置 Keycloak 服务器&lt;/h2&gt;

&lt;p&gt;启动 Keycloak Server 之后，我们访问&lt;code&gt;https://[keycloak service url]/auth/admin/&lt;/code&gt;，使用环境变量中设置的用户名密码登录，进行后续的安全设置，界面如图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/keycloak-main.png&#34; alt=&#34;Keycloak main&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;创建登录域&#34;&gt;创建登录域&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;鼠标在左上角的&lt;code&gt;Master&lt;/code&gt;字样上悬停，在弹出的菜单中选择&lt;code&gt;Add Realm&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在左侧菜单&lt;code&gt;Configure&lt;/code&gt;下面，打开&lt;code&gt;Clients&lt;/code&gt;项，点击&lt;code&gt;Create&lt;/code&gt;设置&lt;code&gt;httpbin&lt;/code&gt;作为 Client ID 并保存。&lt;/li&gt;
&lt;li&gt;设置&lt;code&gt;Access Type&lt;/code&gt;为&lt;code&gt;confidential&lt;/code&gt;，&lt;code&gt;Valid Redirect URIs&lt;/code&gt;设置为&lt;code&gt;*&lt;/code&gt;，并保存。&lt;/li&gt;

&lt;li&gt;&lt;p&gt;打开&lt;code&gt;Installation&lt;/code&gt;标签，选择&lt;code&gt;Keycloak OIDC JSON&lt;/code&gt;，并拷贝显示出来的 JSON 代码，其中的部分内容可能不一致。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
&amp;quot;realm&amp;quot;: &amp;quot;httpbin&amp;quot;,
&amp;quot;auth-server-url&amp;quot;: &amp;quot;https://[keycloak-server]/auth&amp;quot;,
&amp;quot;ssl-required&amp;quot;: &amp;quot;external&amp;quot;,
&amp;quot;resource&amp;quot;: &amp;quot;httpbin&amp;quot;,
&amp;quot;credentials&amp;quot;: {
  &amp;quot;secret&amp;quot;: &amp;quot;d97cfa70-8eb1-443a-8320-4cde9da34de6&amp;quot;
},
&amp;quot;confidential-port&amp;quot;: 0
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;创建用户&#34;&gt;创建用户&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;在左侧的&lt;code&gt;Configure&lt;/code&gt;菜单上打开&lt;code&gt;Roles&lt;/code&gt;页面，点击&lt;code&gt;Add role&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;设置角色名称为&lt;code&gt;httpbin-role&lt;/code&gt;，保存。&lt;/li&gt;
&lt;li&gt;在左手的&lt;code&gt;Manage&lt;/code&gt;菜单中，打开&lt;code&gt;Users&lt;/code&gt;页面，点击&lt;code&gt;Add user&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;填写表单，设置&lt;code&gt;Email verified&lt;/code&gt;为&lt;code&gt;ON&lt;/code&gt;，保存内容。&lt;/li&gt;
&lt;li&gt;打开这一用户的&lt;code&gt;Role mappings&lt;/code&gt;标签，在&lt;code&gt;Available Roles&lt;/code&gt;列表中选择角色&lt;code&gt;httpbin-role&lt;/code&gt;，点击&lt;code&gt;Add selected&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这样我们就完成了登录域的创建，并为后面将要启动的 httpbin 应用创建了相关的角色和用户。&lt;/p&gt;

&lt;h2 id=&#34;部署应用&#34;&gt;部署应用&lt;/h2&gt;

&lt;p&gt;根据前面的流程图，我们需要把 keycloak-proxy 组件用 sidecar 的方式和 httpbin 集成在一起，用反向代理的形式拦截请求，完成登录任务。&lt;/p&gt;

&lt;h3 id=&#34;创建-proxy-配置&#34;&gt;创建 proxy 配置&lt;/h3&gt;

&lt;p&gt;Proxy 配置文件内容可以参考&lt;a href=&#34;http://www.keycloak.org/docs/3.0/server_installation/topics/proxy.html&#34; target=&#34;_blank&#34;&gt;官方文档（链接 2）&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;keycloak-proxy 需要一个配置文件&lt;code&gt;/opt/jboss/conf&lt;/code&gt;，这里我们使用 configmap 的形式将其加载进来，配置文件的内容如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;target-url&amp;quot;: &amp;quot;http://localhost:8000&amp;quot;,
    &amp;quot;send-access-token&amp;quot;: true,
    &amp;quot;bind-address&amp;quot;: &amp;quot;0.0.0.0&amp;quot;,
    &amp;quot;http-port&amp;quot;: &amp;quot;8080&amp;quot;,
    &amp;quot;applications&amp;quot;: [
        {
            &amp;quot;base-path&amp;quot;: &amp;quot;/&amp;quot;,
            &amp;quot;adapter-config&amp;quot;: {
                &amp;quot;realm&amp;quot;: &amp;quot;httpbin&amp;quot;,
                &amp;quot;auth-server-url&amp;quot;: &amp;quot;https://[keycloak-server]/auth&amp;quot;,
                &amp;quot;ssl-required&amp;quot;: &amp;quot;external&amp;quot;,
                &amp;quot;resource&amp;quot;: &amp;quot;httpbin&amp;quot;,
                &amp;quot;credentials&amp;quot;: {
                &amp;quot;secret&amp;quot;: &amp;quot;d97cfa70-8eb1-443a-8320-4cde9da34de6&amp;quot;
                }
            }
        ,
        &amp;quot;constraints&amp;quot;: [
                {
                    &amp;quot;pattern&amp;quot;: &amp;quot;/*&amp;quot;,
                    &amp;quot;roles-allowed&amp;quot;: [
                        &amp;quot;httpbin-role&amp;quot;
                    ]
                }
            ]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这一配置要求 Proxy 代理本机 8000 端口（httpbin 的服务端口）的通信，并以 0.0.0.0:8080 对外提供服务，secret 字段内容来自于上面&lt;code&gt;Keycloak OIDC JSON&lt;/code&gt;的输出。&lt;/p&gt;

&lt;h3 id=&#34;创建-httpbin-deployment&#34;&gt;创建 httpbin deployment&lt;/h3&gt;

&lt;p&gt;在原有的应用部署的基础上，需要加入两个内容：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;加载 ConfigMap&lt;/li&gt;
&lt;li&gt;加入 Sidecar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;节选 yaml 代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;containers:
- image: citizenstig/httpbin
imagePullPolicy: IfNotPresent
name: httpd
ports:
- containerPort: 8000
    name: http-httpbin
- image: jboss/keycloak-proxy:3.4.2.Final
name: httpbin-proxy
volumeMounts:
- mountPath: /opt/jboss/conf
    name: config
ports:
- containerPort: 8080
    name: http-proxy
volumes:
- name: config
configMap:
    name: httpbin-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里完成了上述的任务。&lt;/p&gt;

&lt;h3 id=&#34;创建服务&#34;&gt;创建服务&lt;/h3&gt;

&lt;p&gt;上面创建的 Deployment 之中有了两个端口，8080 是 keycloak 端口；8000 是 httpbin 端口，为了达到认证目的，服务应该指向 keycloak proxy 所在的 8080 端口。&lt;/p&gt;

&lt;h2 id=&#34;测试&#34;&gt;测试&lt;/h2&gt;

&lt;p&gt;依次完成业务应用部署之后，就可以进行测试了。&lt;/p&gt;

&lt;p&gt;在浏览器打开 httpbin 服务，会看到对这一服务的访问会被转向 Keycloak 的登录页面。如果输入的是管理员的账号密码，是无法成功访问服务的；而输入我们新建账号的登录凭据，则可以顺利返回。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://gist.github.com/fleeto/e4d2996b3c0dba41831504747974cf0a&#34; target=&#34;_blank&#34;&gt;所有 yaml 代码（链接 3）&lt;/a&gt;&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;https://www.keycloak.org/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://www.keycloak.org/docs/3.0/server_installation/topics/proxy.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://gist.github.com/fleeto/e4d2996b3c0dba41831504747974cf0a&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Kubeadm 离线安装器 1.10</title>
      <link>/post/kubeadm-offline-installer/</link>
      <pubDate>Mon, 02 Apr 2018 05:30:37 +0800</pubDate>
      <guid>/post/kubeadm-offline-installer/</guid>
      <description>

&lt;h2 id=&#34;项目地址-https-github-com-fleeto-kubeadm-offline-installer&#34;&gt;项目地址：&lt;a href=&#34;https://github.com/fleeto/kubeadm-offline-installer&#34; target=&#34;_blank&#34;&gt;https://github.com/fleeto/kubeadm-offline-installer&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id=&#34;更新内容&#34;&gt;更新内容&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;升级到了 Kubernetes 1.10。&lt;/li&gt;
&lt;li&gt;使用 CoreDNS 代替了原来的 KubeDNS。&lt;/li&gt;
&lt;li&gt;升级 Calico 网络插件为 3.0.4 版。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;仅在 centos 7 minimal 测试通过。&lt;/p&gt;

&lt;h2 id=&#34;使用方法&#34;&gt;使用方法：&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;需要 2.4.0 以上版本的 ansible。&lt;/li&gt;
&lt;li&gt;从 &lt;a href=&#34;https://github.com/fleeto/kubeadm-offline-installer/releases&#34; target=&#34;_blank&#34;&gt;Releases 页&lt;/a&gt; 下载和解压所需版本。&lt;/li&gt;
&lt;li&gt;编写 Host 文件，分组方式可以参考 &lt;code&gt;hosts/hosts.sample&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;group_vars/all&lt;/code&gt; 中有部分可以修改的变量。&lt;/li&gt;
&lt;li&gt;运行脚本。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/local/bin&lt;/code&gt; 目录下会生成初始化和加入集群的脚本。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/kubernetes&lt;/code&gt; 目录中包含证书和配置文件。&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 1.10 的秘宝</title>
      <link>/post/hidden-gems-1.10/</link>
      <pubDate>Wed, 28 Mar 2018 20:22:26 +0800</pubDate>
      <guid>/post/hidden-gems-1.10/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://blog.jetstack.io/blog/hidden-gems-1.10/&#34; target=&#34;_blank&#34;&gt;Kubernetes 1.10: Hidden Gems&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 发布了 1.10 版本。这是各位贡献者和发布团队的的又一次胜利。&lt;/p&gt;

&lt;p&gt;在 Jetstack，无论是为客户服务的 K8S 项目、或是为各级别的用户提供 K8S 培训，又或者向 K8S 社区贡献我们开发的代码，都有着同样的目的，就是将 Kubernetes 推向巅峰。我们一直在跟进这一杰出项目，并保持对其开发过程密切关注。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://blog.kubernetes.io/2018/03/kubernetes-1.10-stabilizing-storage-security-networking.html&#34; target=&#34;_blank&#34;&gt;官方博客（注 1）&lt;/a&gt;中介绍了 1.10 版本中的关键特性，按照以往惯例，我们请我们的工程师分享一下他们在新版本中发现的令人激动、期待的新功能。&lt;/p&gt;

&lt;h2 id=&#34;设备插件&#34;&gt;设备插件&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Matt Turner&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;设备插件系统现在进入了 Beta 阶段。这一能力可以用附加的任意维度来定义节点的容量，不再只限于 CPU 和内存容量。例如一个节点可能会申明其装有三块 GPU 以及一块高性能网卡；Pod 通过标准的&lt;code&gt;resources&lt;/code&gt;方式来申请 1 块 GPU，因此该 Pod 必须调度到一个具有空闲 GPU 的节点上。这一插件及其 API 会在为 Pod 提供使用之前，会对这些资源进行初始化和广播。&lt;/p&gt;

&lt;p&gt;nVidia 已经提供了一个用于管理自家 GPU 的插件。请求两块 GPU 的语句如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;resources:
    limits:
      nvidia.com/gpu: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;coredns&#34;&gt;CoreDNS&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Charlie Egan&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;1.10 把 KubeDNS 变成了一个可插接组件。这样就能够更方便的使用其他工具来进行服务发现。其中的一个选项就是 CoreDNS，CoreDNS 使用一个原生插件实现了 Kubernetes 服务发现规范。他是一个单进程应用，支持缓存和健康检查（这样就无需在 DNS Pod 中使用 dnsmasq 或者是 healthz 容器了）。CoreDNS 插件在 1.10 中提升到了 Beta 阶段，最终会成为 Kubernetes 的缺省选项。&lt;/p&gt;

&lt;h2 id=&#34;每-pod-中的-pid-数量限制&#34;&gt;每 Pod 中的 Pid 数量限制&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/57973&#34; target=&#34;_blank&#34;&gt;1.10 中的一个新的 Alpha 特性（注 2）&lt;/a&gt;就是控制每 Pod 中 Pid 总数的能力。Linux 核心提供了&lt;a href=&#34;https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt&#34; target=&#34;_blank&#34;&gt;进程数控制器&lt;/a&gt;，可以附着在 cgroup 上，在超出特定限制时停止所有新建任务。现在这一核心功能暴露给了集群运维，能够有效的避免一些恶意的或者意外造成的 Fork 炸弹对集群产生不良影响。&lt;/p&gt;

&lt;p&gt;在 kubelet 的&lt;code&gt;--feature-gates=&lt;/code&gt;参数中加入&lt;code&gt;SupportPodPidsLimit=true&lt;/code&gt;，就激活了这一功能。目前只允许运维通过 kubelet 的&lt;code&gt;--pod-max-pids&lt;/code&gt;参数来给每节点定义一个简单的上限数量。可能这种硬性限制并不适用于所有场景，在一些情况下会需要超过这一限制。未来也许会让这一选项更具弹性；一个可能的扩展方式就是制定一个上限和下限，让用户在 Pod 中使用一个布尔字段来选择限制。&lt;/p&gt;

&lt;p&gt;这一功能为工作负载提供了一种新的重要的隔离机制，他的后续发展让人非常期待。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Louis Taylor&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;1.10 加入了共享进程命名空间的 Alpha 支持。要试用这一特性，需要在 apiserver 以及 kubelet 中启用&lt;code&gt;PodShareProcessNamespace&lt;/code&gt;。启用之后就可以在 Pod 中设置&lt;code&gt;shareProcessNamespace&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: shared-pid
spec:
  shareProcessNamespace: true
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在 Pod 内共享 PID 命名空间有一些后果。最重要的是，在容器内的进程，对同一 Pod 内的其他所有容器是可见的，并且可以跨越容器边界向进程发送信号。这使得 Sidecar 容器更加强大（例如发送一个&lt;code&gt;SIGHUP&lt;/code&gt;来重新载入配置）。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Josh Van Leeuwen&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;1.10 中多出一些 Alpha 特性，其中包含了 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/55168&#34; target=&#34;_blank&#34;&gt;CRD 的子资源（注 4）&lt;/a&gt;，分别是&lt;code&gt;/status&lt;/code&gt;和&lt;code&gt;/scale&lt;/code&gt;。和其他资源一样，他们提供了独立的 API 端点来修改内容。不但你的资源现在可以和&lt;code&gt;HorizontalPodAutoscaler&lt;/code&gt;这样的系统进行交互，而且还给用户和控制器状态数据提供了更好的访问控制能力。例如控制器具备的对资源状态的变更和销毁能力就可以不分配给用户。&lt;/p&gt;

&lt;p&gt;为 CRD 加入&lt;code&gt;/status&lt;/code&gt;和&lt;code&gt;/scale&lt;/code&gt;，只要加入如下代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;subresources:
  status: {}
  scale:
    specReplicasPath: .spec.replicas
    statusReplicasPath: .status.replicas
    labelSelectorPath: .status.labelSelector
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;外部自定义指标&#34;&gt;外部自定义指标&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Matt Bates&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;第一个版本的 HPA（v1）只能基于对 CPU 使用率的监控进行。虽然有用，但是 CPU 并不总是应用伸缩的唯一标准。Kubernetes 1.6 中加入的 HPA v2，可以用自定义指标进行伸缩。我们在 &lt;a href=&#34;https://blog.jetstack.io/blog/resource-and-custom-metrics-hpa-v2/&#34; target=&#34;_blank&#34;&gt;Kubernetes 1.8 的博客（注 5）&lt;/a&gt;中介绍了关于资源指标 API、自定义指标 API、以及 HPA v2 的相关内容。&lt;/p&gt;

&lt;p&gt;自定义指标能够描述来自受 HPA 关注的 Pod 的指标，包括资源（CPU、内存）或者对象（服务或者 Ingress）。但是这些选项对于集群之外的基础设施层面是不适用的。最近的客户接触中，就收到了根据 Google 云发布/订阅队列长度进行伸缩的需要。&lt;/p&gt;

&lt;p&gt;1.10 中，现在有了一个 Alpha 阶段的 HPA v2 API，用于支持外部指标。例如上述的根据发布/订阅队列长度进行伸缩的需求大概可以这样实现：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta2
spec:
  scaleTargetRef:
    kind: ReplicationController
    name: Worker
  minReplicas: 2
  maxReplicas: 10
  metrics:
   - type: External
     external:
       metricName: queue_messages_ready
       metricSelector:
         matchLabels:
           queue: worker_tasks
       targetAverageValue: 30
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;HPA 需要一个附加的 API Server，注册为&lt;code&gt;APIService&lt;/code&gt;，并通过查询发布订阅队列，实现自定义指标 API。&lt;/p&gt;

&lt;h2 id=&#34;kubectl-get-和-describe-输出的自定义&#34;&gt;kubectl &lt;code&gt;get&lt;/code&gt;和&lt;code&gt;describe&lt;/code&gt;输出的自定义&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;James Munnelly&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes 1.10 的&lt;code&gt;kubectl get&lt;/code&gt;和&lt;code&gt;kubectl describe&lt;/code&gt;的输出生成过程有了一个很小很重要的变化。&lt;/p&gt;

&lt;p&gt;过去，&lt;a href=&#34;https://github.com/jetstack/cert-manager&#34; target=&#34;_blank&#34;&gt;Cert-Manager（注 6）&lt;/a&gt;或者 &lt;a href=&#34;https://github.com/jetstack/navigator&#34; target=&#34;_blank&#34;&gt;Navigator（注 7）&lt;/a&gt;这样的第三方 Kubernetes 扩展总会显示一些这样的信息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ kubectl get certificates
NAME        AGE
prod-tls    4h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然而现在的版本中，可以通过对扩展的配置来展示更多有用的信息，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ kubectl get certificates
NAME        STATUS    EXPIRY        ISSUER
prod-tls    Valid     2018-05-03    letsencrypt-prod

$ kubectl get elasticsearchclusters
NAME       HEALTH    LEADERS    DATA    INGEST
logging    Green     3/3        4/4     2/2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样给 API 扩展带来一种原生的风格，让用户能够快速的获取有意义的数据。&lt;/p&gt;

&lt;h2 id=&#34;卷调度和本地存储&#34;&gt;卷调度和本地存储&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Richard Wall&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;新版本中，我们很高兴地看到&lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/59391&#34; target=&#34;_blank&#34;&gt;本地存储升级到 Beta 阶段（注 8）&lt;/a&gt;，并且&lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/59391&#34; target=&#34;_blank&#34;&gt;缺省启用了卷调度（注 8）&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;相关的 API 发生了一些变化：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/volumes/#local&#34; target=&#34;_blank&#34;&gt;PV（注 9）&lt;/a&gt;加入了&lt;code&gt;PersistentVolume.Spec.NodeAffinity&lt;/code&gt;字段，其中应包含本地节点的主机名。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/storage-classes/#local&#34; target=&#34;_blank&#34;&gt;StorageClass（注 10）&lt;/a&gt;新增了&lt;code&gt;StorageClass.volumeBindingMode: WaitForFirstConsumer&lt;/code&gt;选项，要求 Kubernetes 等到 Pod 和 PV 成功完成调度规划之后才进行绑定。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/jetstack/navigator/&#34; target=&#34;_blank&#34;&gt;在 Kubernetes 集群中使用 Navigator 管理 Cassandra 数据库（注 11）&lt;/a&gt;就使用这一特性。如果为每个可用的 SSD 加入一个 PV，并且如果 PV 设置了必要的节点亲和性，Kubernetes 会综合 PV 的调度决策，来确定把 Pod 分配到具有未使用 SSD 的节点上去。我们会就这个话题另外写一篇博客。&lt;/p&gt;

&lt;p&gt;另外我还要推荐阅读 &lt;a href=&#34;https://engineering.salesforce.com/provisioning-kubernetes-local-persistent-volumes-61a82d1d06b0&#34; target=&#34;_blank&#34;&gt;Provisioning Kubernetes Local Persistent Volumes（注 12）&lt;/a&gt;，本文描述了一种利用 DaemonSet 自动发现和准备本地卷的优雅方式，以及&lt;a href=&#34;https://github.com/kubernetes-incubator/external-storage/tree/master/local-volume&#34; target=&#34;_blank&#34;&gt;local PV provisioner（注 13）&lt;/a&gt;。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;http://blog.kubernetes.io/2018/03/kubernetes-1.10-stabilizing-storage-security-networking.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/kubernetes/kubernetes/pull/57973&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/kubernetes/kubernetes/pull/55168&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://blog.jetstack.io/blog/resource-and-custom-metrics-hpa-v2/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/jetstack/cert-manager&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/jetstack/navigator&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/kubernetes/kubernetes/pull/59391&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://kubernetes.io/docs/concepts/storage/volumes/#local&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://kubernetes.io/docs/concepts/storage/storage-classes/#local&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/jetstack/navigator/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://engineering.salesforce.com/provisioning-kubernetes-local-persistent-volumes-61a82d1d06b0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/kubernetes-incubator/external-storage/tree/master/local-volume&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 网络策略指南</title>
      <link>/post/network-policy-for-k8s/</link>
      <pubDate>Wed, 28 Feb 2018 22:57:14 +0800</pubDate>
      <guid>/post/network-policy-for-k8s/</guid>
      <description>

&lt;p&gt;&lt;img src=&#34;images/have-fun.gif&#34; alt=&#34;You can get stuff like this&#34; /&gt;
&lt;em&gt;有了网络策略，你也做得到！&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;这里包含一系列的 &lt;a href=&#34;https://kubernetes.io/docs/concepts/services-networking/network-policies/&#34; target=&#34;_blank&#34;&gt;Kubernetes 网络策略&lt;/a&gt;的相关用例以及相关的 YAML 文件。想要知道如何在 Kubernetes 中对某一应用的流量进行阻拦或者限制，请继续阅读。&lt;/p&gt;

&lt;p&gt;尝试网络策略的最简单方法就是创建一个新的 &lt;a href=&#34;https://cloud.google.com/container-engine&#34; target=&#34;_blank&#34;&gt;Google Container Engine 集群&lt;/a&gt;。在现有集群上应用网络策略，可能会造成网络中断。在成文期间，多数云供应商尚未提供内置的网络策略支持。&lt;/p&gt;

&lt;p&gt;如果你对网络策略一窍不通，建议首先阅读我的文章：&lt;a href=&#34;https://ahmet.im/blog/kubernetes-network-policy/&#34; target=&#34;_blank&#34;&gt;Securing Kubernetes Cluster Networking&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&#34;开始之前&#34;&gt;开始之前&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;创建集群&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;基础&#34;&gt;基础&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;拦截到一个应用的所有流量&lt;/li&gt;
&lt;li&gt;限制到一个应用的流量&lt;/li&gt;
&lt;li&gt;允许所有目标为某个应用的流量&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;命名空间&#34;&gt;命名空间&lt;/h3&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;允许来自某命名空间某些 Pod 的流量&lt;/li&gt;
&lt;li&gt;限制当前命名空间内到某应用的流量&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;控制外部客户端流量&#34;&gt;控制外部客户端流量&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;允许外部客户端的流量&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;进阶&#34;&gt;进阶&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;只允许目标为指定端口的流量&lt;/li&gt;
&lt;li&gt;用多个选择器选择允许通过的流量&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;控制外发流量-egress&#34;&gt;控制外发流量（Egress）&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;拦截来自某应用的外发流量&lt;/li&gt;
&lt;li&gt;拦截命名空间内所有白名单之外的 Egress 流量&lt;/li&gt;
&lt;li&gt;限制来从指定应用到某些 Pod 的 egress 流量。&lt;/li&gt;
&lt;li&gt;只允许目标是命名空间内某 Pod 的流量。&lt;/li&gt;
&lt;li&gt;拦截外部的 egress 流量&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&#34;作者&#34;&gt;作者&lt;/h2&gt;

&lt;p&gt;Ahmet Alp Balkan (&lt;a href=&#34;https://twitter.com/ahmetb&#34; target=&#34;_blank&#34;&gt;@ahmetb&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Copyright 2017, Google Inc. Distributed under Apache License Version 2.0 ,see &lt;a href=&#34;LICENSE&#34; target=&#34;_blank&#34;&gt;LICENSE&lt;/a&gt; for details.&lt;/p&gt;

&lt;p&gt;声明: 这并非 Google 官方文档。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 Azure 上运行 Istio 的注意事项</title>
      <link>/post/istion-on-azure-k8s/</link>
      <pubDate>Fri, 02 Feb 2018 23:04:20 +0800</pubDate>
      <guid>/post/istion-on-azure-k8s/</guid>
      <description>&lt;ul&gt;
&lt;li&gt;ACS Engine: v0.12.4&lt;/li&gt;
&lt;li&gt;Istio: v0.5.0&lt;/li&gt;
&lt;li&gt;Kubernetes: v1.8.7&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在 Istio 注入之后，生成的 Init 容器中会有 RunAs 的 SecurityContext，而 ACS Engine 的缺省 admission 包含了&lt;code&gt;SecurityContextDeny&lt;/code&gt;，会拒绝这一选项，造成 Istio Workload 无法运行，&lt;/p&gt;

&lt;p&gt;解决方法很简单，只要在定义文件中修改 api server 配置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;quot;properties&amp;quot;: {
  &amp;quot;orchestratorProfile&amp;quot;: {
    &amp;quot;kubernetesConfig&amp;quot;: {
        &amp;quot;apiServerConfig&amp;quot;: {
            &amp;quot;--request-timeout&amp;quot;: &amp;quot;30s&amp;quot;,
            &amp;quot;--admission-control&amp;quot;: &amp;quot;NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota,DenyEscalatingExec,AlwaysPullImages&amp;quot;
        }

&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Node 重伤之后</title>
      <link>/post/node-downtime/</link>
      <pubDate>Wed, 20 Dec 2017 01:58:49 +0800</pubDate>
      <guid>/post/node-downtime/</guid>
      <description>

&lt;p&gt;很多人都知道 Kubernetes 能自动维护失效 Pod、防止服务中断、剔除故障节点 BLABLA 的一堆高大上功能。但节点故障之后，会对运行在故障节点上的容器、以及依赖容器的服务造成什么影响，是应该了解的，这样才能有针对性的进行监控设置、部署安排、故障处理等工作。&lt;/p&gt;

&lt;p&gt;水平有限，这里不谈原理，只说说症状和一些相关的调整。&lt;/p&gt;

&lt;h2 id=&#34;测试环境&#34;&gt;测试环境&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 1.7.10，三节点，其中 Master 节点被 Taint.

&lt;ul&gt;
&lt;li&gt;10.211.55.11(Master)&lt;/li&gt;
&lt;li&gt;10.211.55.12&lt;/li&gt;
&lt;li&gt;10.211.55.13&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;CentOS 7&lt;/li&gt;
&lt;li&gt;Docker 1.12.3&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;实验设计&#34;&gt;实验设计&lt;/h2&gt;

&lt;p&gt;Docker、Kubelet 以及 Kube-Proxy 是 Node 的标准组件，我们准备一个 2 Replicas 的 Deployment 作为测试目标，使用 NodePort 方式暴露 HTTP 端口。具体代码见附录。&lt;/p&gt;

&lt;p&gt;使用 &lt;code&gt;systemctl&lt;/code&gt; 关停目标服务，使用 &lt;code&gt;watch -n5 kubectl get pods,nodes,deploy&lt;/code&gt; 以及 &lt;code&gt;watch -n5 kubectl &amp;quot;describe svc httpbin | grep -i endp&amp;quot;&lt;/code&gt; 命令持续检查相关工作负载的情况。同时可以使用 &lt;code&gt;curl&lt;/code&gt; 命令检查服务是否存活。&lt;/p&gt;

&lt;h2 id=&#34;kube-proxy&#34;&gt;Kube-Proxy&lt;/h2&gt;

&lt;p&gt;选择登录一个节点，例如 &lt;code&gt;10.211.55.12&lt;/code&gt;，使用 &lt;code&gt;kubectl stop kube-proxy&lt;/code&gt; 关闭之后可以看到，各个 &lt;code&gt;kubectl get&lt;/code&gt; 命令返回结果都正常，似乎未受影响；然而使用 &lt;code&gt;curl&lt;/code&gt; 进行逐个节点的 NodePort 进行验证的时候，会发现停掉 Proxy 的地址是无法提供服务的。&lt;/p&gt;

&lt;h3 id=&#34;监控&#34;&gt;监控&lt;/h3&gt;

&lt;p&gt;除了监控进程/服务之外，Kube-Proxy 还提供了几个可以用于监控的参数&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--healthz-bind-address&lt;/code&gt;：健康检测地址和端口，缺省为 0.0.0.0:10256&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--healthz-port&lt;/code&gt;：健康检测端口，缺省 10256，设置为 0 则关闭。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;curl http://127.0.0.1:10256/healthz | jq&lt;/code&gt; 会看到返回的健康数据：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;lastUpdated&amp;quot;: &amp;quot;2017-12-19 16:05:27.333531431 +0800 CST&amp;quot;,
  &amp;quot;currentTime&amp;quot;: &amp;quot;2017-12-19 16:05:33.73732624 +0800 CST&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&amp;mdash; &lt;code&gt;--metrics-bind-address&lt;/code&gt; 用于提供监控指标的地址和端口，缺省为 127.0.0.1:10249，返回内容可用于 Prometheus。例如：&lt;code&gt;curl -s  http://127.0.0.1:10249/metrics  | more&lt;/code&gt;会看到：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;......
# HELP http_request_size_bytes The HTTP request sizes in bytes.
# TYPE http_request_size_bytes summary
http_request_size_bytes{handler=&amp;quot;prometheus&amp;quot;,quantile=&amp;quot;0.5&amp;quot;} 64
http_request_size_bytes{handler=&amp;quot;prometheus&amp;quot;,quantile=&amp;quot;0.9&amp;quot;} 64
http_request_size_bytes{handler=&amp;quot;prometheus&amp;quot;,quantile=&amp;quot;0.99&amp;quot;} 64
http_request_size_bytes_sum{handler=&amp;quot;prometheus&amp;quot;} 192
http_request_size_bytes_count{handler=&amp;quot;prometheus&amp;quot;} 3
......
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;kubelet&#34;&gt;Kubelet&lt;/h2&gt;

&lt;p&gt;Kubelet 情况比 kube-proxy 复杂一些。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先使用&lt;code&gt;kubectl get po -o wide&lt;/code&gt;，确认 Pod 所在节点。&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;systemctl stop kubelet&lt;/code&gt;停止 kubelet 服务。&lt;/li&gt;
&lt;li&gt;观察&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;首先我们会看到，经过约半分钟以后，该节点变为&lt;code&gt;NotReady&lt;/code&gt;状态。Deploy 对象的 Available 字段数字会减 1，服务的&lt;code&gt;Endpoints&lt;/code&gt;列表减少一个。；但是 Pod 状态保持在&lt;code&gt;Running&lt;/code&gt;，&lt;/p&gt;

&lt;p&gt;五分钟左右，Pod 进入&lt;code&gt;Unknown&lt;/code&gt;状态，开始尝试启动新 Pod。&lt;/p&gt;

&lt;h3 id=&#34;存活检测和就绪检测&#34;&gt;存活检测和就绪检测&lt;/h3&gt;

&lt;p&gt;测试中我们使用的 yaml 中并没有设置这两个内容，事实上，这两个检测是由 kubelet 执行的，对上述行为并无影响。&lt;/p&gt;

&lt;h3 id=&#34;参数调整&#34;&gt;参数调整&lt;/h3&gt;

&lt;p&gt;真正影响上面的行为的是&lt;code&gt;kube-controller-manager&lt;/code&gt;的两个参数：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--pod-eviction-timeout&lt;/code&gt;：缺省为 5m，五分钟，在 Pod 驱逐行为的超时时间。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--node-monitor-grace-period&lt;/code&gt;：缺省为 40s，也就是 40 秒，无响应 Node 在标记为 NotReady 之前的等候时间。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;监控-1&#34;&gt;监控&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--healthz-bind-address&lt;/code&gt;：健康监测地址，缺省为&lt;code&gt;127.0.0.1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--healthz-port&lt;/code&gt;：健康检测端口，缺省为 10248。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;curl 访问该地址，会得到响应：&lt;code&gt;ok&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;另外如果使用&lt;code&gt;kube-metrics exporter&lt;/code&gt;进行集群监控，可以关注 RC、Deploy 等对象的可用实例数量。&lt;/p&gt;

&lt;h2 id=&#34;docker&#34;&gt;Docker&lt;/h2&gt;

&lt;p&gt;Docker 的情况其实跟 Kubelet 类似，但是结果会更严重：在 Endpoint 被排除之前，路由到故障节点的流量会发生故障。&lt;/p&gt;

&lt;h2 id=&#34;测试之外&#34;&gt;测试之外&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;多 Pod 不仅对性能有好处，在极端情况下能降低故障节点对服务整体效果的影响。&lt;/li&gt;
&lt;li&gt;建议采用节点互斥的方式进行部署。&lt;/li&gt;
&lt;li&gt;对关键组件的监控，应该建立从进程到指标的多级监控，减小服务故障的时间窗口。&lt;/li&gt;
&lt;li&gt;Pod 的存活和健康监测，对于容器内的应用是有效的，应该推荐。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;附录&#34;&gt;附录&lt;/h2&gt;

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

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
spec:
  ports:
  - name: http
    port: 80
    nodePort: 30080
  selector:
    app: httpbin
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: httpbin-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: httpd
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
&lt;/code&gt;&lt;/pre&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>
    
    <item>
      <title>SoundCloud 如何使用 HAProxy 和 Kubernetes 处理用户流量</title>
      <link>/post/soundcloud-haproxy-k8s/</link>
      <pubDate>Tue, 12 Dec 2017 09:03:37 +0800</pubDate>
      <guid>/post/soundcloud-haproxy-k8s/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://developers.soundcloud.com/blog/how-soundcloud-uses-haproxy-with-kubernetes-for-user-facing-traffic&#34; target=&#34;_blank&#34;&gt;How SoundCloud uses HAProxy with Kubernetes for user-facing traffic&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;两年前 SoundCloud 开始了将我们的自研部署平台 &lt;a href=&#34;http://gotocon.com/zurich-2013/presentation/Building%20an%20in-house%20Heroku&#34; target=&#34;_blank&#34;&gt;Bazooka&lt;/a&gt; 迁移到 &lt;a href=&#34;https://kubernetes.io/&#34; target=&#34;_blank&#34;&gt;Kubernetes&lt;/a&gt; 的尝试。Kubernetes 将容器化应用的部署、伸缩和管理都进行了自动化。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/cdn-haproxy-k8s.png&#34; alt=&#34;haproxy-k8s-cdn&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;问题&#34;&gt;问题&lt;/h2&gt;

&lt;p&gt;用户流量的路由，是这类动态平台需要面对的一个重大挑战：把来自用户的 API 和网站访问路由到运行在 Kubernetes 的 Pod 上。&lt;/p&gt;

&lt;p&gt;多数 SoundCloud 运行在一个物理机环境下，所以我们无法享受 Kubernetes Loadblancer 的福利。在基础设施的边际，有一组 HAProxy 服务器使用简单的规则，承担着 SSL 端点的角色，把流量转发给各种内部服务。这些服务器的配置在运行之前是分别进行生成和测试的。有很多内置的保护措施的存在，这一过程会相当耗时，无法跟上 Kubernetes 集群中 Pod 的漂移速度。这样就导致我们的静态 SSL 端点和 Kubernetes 中的动态变化之间的矛盾。&lt;/p&gt;

&lt;h2 id=&#34;过程&#34;&gt;过程&lt;/h2&gt;

&lt;p&gt;起初，我们把端点层配置为转发 HTTP 请求到 HAProxy 为基础的 &lt;a href=&#34;https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers&#34; target=&#34;_blank&#34;&gt;Ingress Controller&lt;/a&gt; 之中，但是这一方案对我们不太合适，Ingress 控制器设计预期是低流量的内部服务，并不可靠。我们的用户生成了很多流量，每一个问题都会导致部分用户无法使用 SoundCloud。在 Kubernetes Ingress 和端点之间，我们有了两层 &lt;a href=&#34;https://en.wikipedia.org/wiki/OSI_model#Layer_7:_Application_Layer&#34; target=&#34;_blank&#34;&gt;L7&lt;/a&gt; 要相互配对，而且经常无法配对。这对我们的开发人员造成很大困扰，增加了不少工作。&lt;/p&gt;

&lt;p&gt;我们还知道 Ingress Controller 无法处理我们部分客户端使用的长连接。&lt;/p&gt;

&lt;p&gt;当 SoundCloud 工程师构建应用时，我们使用一个自定义命令行界面来生成 Namespace、Service、Deployment 和可选的 Ingress Kubernetes 对象。我们添加了一个参数，来把服务改成 &lt;a href=&#34;https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport&#34; target=&#34;_blank&#34;&gt;NodePort 类型&lt;/a&gt;，&lt;/p&gt;

&lt;p&gt;Kubernetes 在集群中找一个没被占用的端口分配给服务，并在集群每个节点上开放这个端口。到任意一个节点的该端口的访问都会被转发给这一服务的某一个实例。(我们生成的 Kubernetes 对象在 Deployment 和 Service 之间有着一对一的关系)。为简便起见，这里就不深入谈论 ReplicaSet、Pod、以及 Endpoint 等 Kubernetes 对象了。&lt;/p&gt;

&lt;p&gt;对服务的这种改变（Ingress 到 NodePort）是不可逆的。Kubernetes 不允许移除服务定义中的 NodePort 字段。我们还在寻求解决办法 —— 这种情况虽然可以通过删除再重建服务的方式来解决，但是这一方式会导致服务中断。&lt;/p&gt;

&lt;p&gt;应用开发者为应用声明集群、命名空间、服务和端口名，生成一个特定的主机名和路径。系统根据这一配置，将来自 SSL 端点、CDN 分发以及 DNS 等的流量转发给应用。&lt;/p&gt;

&lt;h2 id=&#34;实现&#34;&gt;实现&lt;/h2&gt;

&lt;p&gt;当端点配置声成以后，脚本向 Kubernetes 集群查询每个服务的 Node Port，以及 Kubernetes 的节点列表。起初我们把所有节点都加入到端点的配置之中，不过后来证明这是一个问题。&lt;/p&gt;

&lt;p&gt;每个端点会独立的检查每个节点的每个端口的健康状况。几十个端点，几百个 Kubernetes 节点，导致每秒钟上万次的健康检查。这种检查是通过节点进行的，跟服务规模无关，所以即使是很低流量的服务，也需要大量的资源来应对这些健康检查。&lt;/p&gt;

&lt;p&gt;我们因此减少了配置给每个服务的节点数量，不过我们也不希望这个节点数量太少，免得造成性能瓶颈。一个简单的方案就是随机从列表中选择一些节点，但这样的话，这一列表的每次生成，都会发生很大的变化，会跟真正的部署调整造成混淆。所以我们决定使用服务名称和节点地址来进行哈希，为每个服务选择一组固定数量的服务器，但是这个选择在节点不变的情况下也会保持不变。&lt;/p&gt;

&lt;p&gt;我们选择足够多的节点，这样就不必担心一两台节点发生当机，或节点被多个高流量服务重叠使用。&lt;/p&gt;

&lt;p&gt;要替换一个节点，只需要重启 Pipeline，生成并部署新的端点配置。这会花费几个小时，好在是全自动的。因为每个服务都通过有限数量的节点进行路由，因此不能一次性的从同一个服务中移除太多节点。这意味着，我们只能每天从 Kubernetes 集群中替换有限数量的节点，不过目前为止这还没有造成什么问题。&lt;/p&gt;

&lt;h2 id=&#34;node-agent&#34;&gt;Node Agent&lt;/h2&gt;

&lt;p&gt;在短期维护中，例如内核更新需要重启节点，我们需要一种将节点优雅排空的能力。我们为 HAProxy 的 agent-check 协议开发了一个 Agent，监听每个节点的固定端口。为了简单起见，我们希望能够同时移除流量和 Pod，当节点从 Kubernetes 进行隔离之后，新的 Pod 不会被调度在这一节点上，Agent 更新 HAProxy 状态，将流量从这一节点上移除。&lt;/p&gt;

&lt;p&gt;有时我们还要把流量在一个服务的不同 Deployment 之间进行切换，还包括同一集群和不同集群的情况。为了支持这种切换，我们对这个 Agent 进行了扩展。我们在 Kubernetes 的 Service 对象上增加了一些注解(annotations)，以此指示监听新的端口，扩展的 Agent 不仅检查节点的维护状态，还会检查这一服务的优先级。端点配置过程会获取这一信息并据此配置 Agent 的检查行为。应用开发者可以添加多个 Kubernetes 后端到任意的公开主机和路径。每个后端我们都像之前一样利用哈希选择一组节点提供服务。修改服务注解，就能在几秒钟改变各个后端服务的权重。&lt;/p&gt;

&lt;h2 id=&#34;未来&#34;&gt;未来&lt;/h2&gt;

&lt;p&gt;这种基于 NodePort 的路由方式工作的不错，但是也有一些隐忧。一个应用实例之间的负载分配并不均等，我们的应对方式是为应用分配稍多的资源。这一问题上，多分配资源的成本远低于由工程师开发新的负载均衡机制所需要的花费。&lt;/p&gt;

&lt;p&gt;在物理机组成的数据中心运维 Kubernetes 是一个挑战。在公有云服务中，都会提供标准方案；而每个数据中心都会有些不同——尤其是并非从头开始，而是将 Kubernetes 向现有基础设施进行融合的情况下。本文中，我们主要阐述的就是我们应对部分问题的部分解决方法.&lt;/p&gt;

&lt;p&gt;下一步？我们目前考虑的是在不同位置的多个 Kubernetes 集群，为不同用户选择不同集群来达到优化体验的目的。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kube-Node：让 Kubernetes 自行管理节点</title>
      <link>/post/kube-node-for-k8s/</link>
      <pubDate>Sun, 26 Nov 2017 18:37:58 +0800</pubDate>
      <guid>/post/kube-node-for-k8s/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://thenewstack.io/kube-node-let-k8s-cluster-auto-manage-nodes/&#34; target=&#34;_blank&#34;&gt;Kube-Node: Let Your Kubernetes Cluster Auto-Manage Its Nodes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本文是一个系列中的一篇，来自于 CNCF 成员，为奥斯汀 12.6-12.8 的 KubeCon/CloudNativeCon 而做。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://twitter.com/michellenoorali&#34; target=&#34;_blank&#34;&gt;Michelle Noorali&lt;/a&gt; 在今年三月份的&lt;a href=&#34;https://www.thenewstack.io/tag/KubeCon-Europe-2017&#34; target=&#34;_blank&#34;&gt;欧洲 KubeCon&lt;/a&gt; 上的 &lt;a href=&#34;https://www.youtube.com/watch?v=aOQwyN0bTk4&#34; target=&#34;_blank&#34;&gt;KeyNote&lt;/a&gt;中说到：&lt;a href=&#34;https://www.thenewstack.io/tag/Kubernetes&#34; target=&#34;_blank&#34;&gt;Kubernetes&lt;/a&gt; 对开发人员来说还是太难了。原则上来说，开发人员对 Kubernetes 和容器技术是非常热爱的，这一系列的技术让开发人员编写的应用能够到处运行，却无需担心基础设施的差异。但事实上，这一技术体系还是有很多依赖的，这些依赖很大程度上磨灭了用户的热情。&lt;/p&gt;

&lt;p&gt;对开发人员来说，Kubernetes 有个主要的障碍就是他无法对其服务器进行自动管理和伸缩，因此一个工作节点的部署或移除都少不了运维工程师的参与。当然目前有很多节点部署方案，例如 &lt;a href=&#34;https://www.thenewstack.io/tag/Terraform&#34; target=&#34;_blank&#34;&gt;Terraform&lt;/a&gt;、&lt;a href=&#34;http://bit.ly/2pxydLh&#34; target=&#34;_blank&#34;&gt;Chef&lt;/a&gt; 以及 &lt;a href=&#34;http://bit.ly/2k6mjaO&#34; target=&#34;_blank&#34;&gt;Puppet&lt;/a&gt;，这些技术让运维人员的日子好过了点。然而上述技术都需要一些特定领域的知识；目前还不存在一种无须运维接入的通用的跨平台方法。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/deploy-a-node-now.png&#34; alt=&#34;目前的节点部署方法&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 节点的生命周期，同其他的 Kubernetes 资源的生命周期是不一致的，这是 Kubernetes 节点部署和管理的一个主要问题。当下的节点管理生命周期里，节点是在一台服务器加入集群之后创建的。然而一般的 Kubernetes 资源并不是这样的：首先创建资源，然后才加入服务器。&lt;/p&gt;

&lt;p&gt;这种情况是不合理的。为了解决这一问题，来让节点的管理工作纳入正轨，我们决定启动 &lt;a href=&#34;https://github.com/kube-node/kube-machine&#34; target=&#34;_blank&#34;&gt;kube-node&lt;/a&gt; 这一社区项目。我们的目标是为 Kubernetes 开发一个类似 &lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/persistent-volumes/&#34; target=&#34;_blank&#34;&gt;持久卷系统&lt;/a&gt; 的原生节点整合方案。&lt;/p&gt;

&lt;p&gt;持久卷系统从各种云中抽象出了存储的供给和使用的功能。这种抽象把存储卷的生命期从环境中独立出来，令其可以被动态的分配和管理。&lt;/p&gt;

&lt;h2 id=&#34;如何设置-kube-node&#34;&gt;如何设置 Kube-Node&lt;/h2&gt;

&lt;p&gt;类似持久卷的设置，Kube-Node 是一个高级别的抽象系统：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;管理员进行配置的定义&lt;/li&gt;
&lt;li&gt;开发人员可以使用简单的&lt;code&gt;kubectl create node -f node1.yaml&lt;/code&gt;来对集群进行扩容&lt;/li&gt;
&lt;li&gt;Kubernetes 控制节点的生命周期&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为了达成这一目标，我们引入了两个新的 API 资源：NodeClass 和 NodeSet，这些资源由 NodeController 和 NodeSetController 来进行管理。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NodeSet 保证任意时间内都有指定数量的节点正在运行。跟 ReplicaSet 类似，NodeSet 要保障一个节点或者一系列的同质化节点的运行和可用状态。 NodeSet 来自于 NodeClass，其细节定义在 Node 模板中。&lt;/li&gt;
&lt;li&gt;NodeClass 让管理员有了为新节点设置配置的可能性。NodeClass 包括了云供应商和操作系统特定的细节，例如认证信息、机器类型等数据。另外管理员还可以定义服务质量水平和策略。Kubernetes 不能干涉 NodeClass 的定义。&lt;/li&gt;
&lt;li&gt;NodeSetController 会关注 NodeSet，负责节点的创建和删除工作。在目前的实现中，他管理节点资源的创建，并跟 GKE 的节点池进行同步。&lt;/li&gt;
&lt;li&gt;NodeController 关注节点对象并在云端进行服务器的供应。服务器加入集群之后，kubelet 会更新节点资源；类似的，在节点对象被删除的时候，NodeController 会从云端删除这一服务器。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;images/kube-node.png&#34; alt=&#34;kube-node&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;概述和路线&#34;&gt;概述和路线&lt;/h2&gt;

&lt;p&gt;kube-node 是一个社区项目，用于提供通用的 Kubernetes 节点管理手段。目标是为开发者提供一个无需运维介入的、基础设施无关的简单方式，来对集群进行伸缩。其中包含了基于 CustomResourceDefinition 的类型、client-go 为基础的客户端以及 NodeController 和 NodeSetController。&lt;/p&gt;

&lt;p&gt;首先会实现 NodeSetController 和 NodeController 的联动，接下来会和 GKE 节点池进行集成。上面的两个步骤会针对各种不同用例来进行概念的验证。NodeController 的第一步会复用 docker-machine 的一部分代码，称为 &lt;a href=&#34;https://github.com/kube-node/kube-machine&#34; target=&#34;_blank&#34;&gt;kube-machine&lt;/a&gt;，用于和多个不同的云供应商中加载服务器。&lt;/p&gt;

&lt;p&gt;下一步，我们计划把 NodeSet 和 Kubernetes 的自动伸缩功能进行集成，让 NodeController 根据集群负载，能够自动对 NodeSet 的实例数进行设置。这一集成能够让 Kubernetes 在多种不同的平台上实现集群的自动伸缩。另外我们会实现更多的 NodeController，包括 Terraform、Python 或者 AWS。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>搭建高可用 Kubernetes 集群</title>
      <link>/post/ha-kubernetes-cluster/</link>
      <pubDate>Wed, 22 Nov 2017 11:19:59 +0800</pubDate>
      <guid>/post/ha-kubernetes-cluster/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;领导说，只要机器够多，故障是个很平常的事情。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes 能很好的管理容器和节点，所以正常的节点故障或者个别应用的故障是不会影响集群运作的。一旦 apiserver 或者所依赖的 etcd 出现问题，情况就不再乐观了。幸好这两个核心服务都提供了高可用相关能力。同时 controller-manager 以及 scheduler 也都具备通过选举产生 leadership 的机制，这就提供了高可用的基础。下面讲讲 Master 组件的高可用部署方式。&lt;/p&gt;

&lt;h2 id=&#34;部署目标&#34;&gt;部署目标&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;每个 apiserver 都使用不同的负载均衡端点访问整个 etcd 集群。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;每个 controller-manager 和 scheduler 使用各自的 apiserver 进行工作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;客户端通过负载均衡机访问 apiserver。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2 id=&#34;前提条件&#34;&gt;前提条件&lt;/h2&gt;

&lt;p&gt;这里我们假设，ETCD 集群已经成功建立，包含三个节点，采用 https 访问进行访问；Master 节点只有一个。也同样的使用 https 进行访问，集群能够正常工作。&lt;/p&gt;

&lt;p&gt;另外准备若干台服务器安装 haproxy 充当负载均衡。&lt;/p&gt;

&lt;h2 id=&#34;etcd&#34;&gt;ETCD&lt;/h2&gt;

&lt;p&gt;众所周知的是，apiserver 的 &lt;code&gt;--etcd-servers&lt;/code&gt; 参数是可以输入一整个 etcd 集群的，然而在使用中我们发现，一些特定版本的 apiserver 在 第一台 etcd 服务所在节点的特殊故障情况下，虽然 etcd 本身已经能够发现集群健康状态异常，却依然会陷入假死状态，，具体症状是多数 kubectl 的 Workload 相关操作都能完成，然而却不会真正生效。因此我们需要更可靠的对 etcd 集群的访问方式，降低 apiserver 缺陷造成的集群不可用的风险。&lt;/p&gt;

&lt;p&gt;因为采用了 https 方式进行访问，所以这里我们 haproxy 的配置需要使用 tcp 方式。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;不用 https 端点（也就是对外 https 对内 http 的部署）的方式，纯属个人习惯。
在启用 haproxy 之前，注意要把服务端证书的 IP 进行扩展，令其支持新增的负载均衡地址。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;简单粗暴上代码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
frontend  in-etcd
    bind *:12379
    mode tcp
    default_backend etcd-cluster

backend etcd-cluster
    mode tcp
    balance     roundrobin
    option httpchk GET /health
    http-check expect string true
    server app1 10.211.55.9:2379 check check-ssl verify none
    server app2 10.211.55.10:2379 check check-ssl verify none
    server app3 10.211.55.11:2379 check check-ssl verify none
...
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;使用负载均衡节点的 12379 端口对外提供服务。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;前后端均使用 tcp 方式。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;利用 etcd 节点的 &lt;code&gt;/health&lt;/code&gt; 端点进行健康检查，健康标准是返回字符串 &lt;code&gt;true&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;不校验 etcd 服务端证书。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;启用 haproxy 之后，访问 haproxy 的状态页面，可以看到 etcd 集群的代理已经建立。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/etcd.haproxy.png&#34; alt=&#34;etcd-cluster&#34; /&gt;&lt;/p&gt;

&lt;p&gt;任意停止其中一台，会看到在检测窗口过后，对应的服务器状态切换为 &lt;code&gt;DOWN&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;用同样的方式部署其他两台 haproxy 服务器。&lt;/p&gt;

&lt;h2 id=&#34;master-组件&#34;&gt;Master 组件&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;和 etcd 情况一样，apiserver 的服务证书，也需要更新，加入新的 Master 节点以及前端负载均衡的地址。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;首先更改 apiserver 的 etcd 参数，从原来的三台 etcd 变更为单一的 haproxy 地址，例如 &lt;code&gt;https://haproxy1:12379&lt;/code&gt;。修改之后，重新启动服务，可以看到服务正常启动成功。&lt;/p&gt;

&lt;p&gt;接下来，我们将 apiserver、controller-manager 以及 scheduler 的可执行文件、服务定义、相关证书以及配置文件复制到新加入的服务器中，并启用服务。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;每个 apiserver 需要配置各自的 etcd haproxy。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;每个 scheduler 和 controller-manager 都访问各自的 apiserver 服务，并使用参数&lt;code&gt;--leader-elect&lt;/code&gt;启用选举（这一参数是缺省启用的）。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;和 etcd 类似，为 apiserver 配置 haproxy：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;frontend in-apiserver
    bind *:16443
    mode tcp
    default_backend apiserver-cluster

backend apiserver-cluster
    mode tcp
    balance     roundrobin
    option httpchk GET /healthz
    http-check expect string ok
    server app1 10.211.55.9:6443 check check-ssl verify none
    server app2 10.211.55.10:6443 check check-ssl verify none
    server app3 10.211.55.11:6443 check check-ssl verify none
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里我们访问 apiserver 的 &lt;code&gt;/healthz&lt;/code&gt; 端点，如果得到返回内容为 &lt;code&gt;ok&lt;/code&gt;，则判断集群为健康。&lt;/p&gt;

&lt;p&gt;配置后启动 haproxy，查看状态页面，可以看到 apiserver 集群也已经就绪。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/apiserver.proxy_.png&#34; alt=&#34;apiserver-cluster&#34; /&gt;&lt;/p&gt;

&lt;p&gt;最后可以修改 kubectl 以及各个 Node 的 kubelet/kube-proxy apiserver 参数到负载均衡地址。&lt;/p&gt;

&lt;h2 id=&#34;测试&#34;&gt;测试&lt;/h2&gt;

&lt;p&gt;停止任意一台 Master 的服务，包括 apiserver/controller-manager/scheduler，会看到 controller-manager 或 scheduler 的选举过程。如果停掉的是 apiserver，则可以在 haproxy 状态页上看到集群可用性的变化。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 Kubernetes 上使用 Jmeter 运行压力测试</title>
      <link>/post/jmeter-on-k8s/</link>
      <pubDate>Thu, 16 Nov 2017 23:31:55 +0800</pubDate>
      <guid>/post/jmeter-on-k8s/</guid>
      <description>

&lt;p&gt;Kubernetes 的资源和任务调度能力，能给自动化测试提供相当大力的支持，这里以 Jmeter 为例，讲讲如何在 Kubernetes 中使用 Jmeter 进行简单的性能测试。&lt;/p&gt;

&lt;h2 id=&#34;开始之前&#34;&gt;开始之前&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;录制任务：本文所用镜像为 Jmeter 3.x 版本，建议提前录制一个简单的测试任务进行下面的操作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;支持 Jobs 的 Kubernetes 集群，以及缺省的 StorageClass 支持，能够实现 PVC 的动态供应。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;互联网连接。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;试验内容&#34;&gt;试验内容&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;搭建一个 Web DAV 服务，用于提供给 Jmeter 输入输出场所，也便于日后 CI/CD 工具的案例输入或结果输出。&lt;/li&gt;
&lt;li&gt;运行单实例的 Jmeter 测试任务。&lt;/li&gt;
&lt;li&gt;运行集群形式的 Jmeter 测试任务。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;预备存储&#34;&gt;预备存储&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;这一步骤并非强制，完全可以通过 scp 或者 mount 等其他方式来实现&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这里我们做一个 Web DAV 服务，挂载一个 PVC，在其中分为 input 和 output 两个目录，实际使用过程中，可以进一步按照任务或者 Job 对目录进行更详尽的规划。&lt;/p&gt;

&lt;p&gt;首先创建名为&lt;code&gt;jmeter-task&lt;/code&gt;的存储卷：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: jmeter-task
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;存储卷创建之后，可以使用 &lt;code&gt;cadaver&lt;/code&gt; 或者 &lt;code&gt;WinSCP&lt;/code&gt; 等工具去建立目录。&lt;/p&gt;

&lt;p&gt;接下来上传 &lt;code&gt;*.jmx&lt;/code&gt; 文件，到 &lt;code&gt;input&lt;/code&gt; 目录之中，这里我录制的一个持续访问京东首页的任务，命名为 &lt;code&gt;jd.jmx&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;单实例测试&#34;&gt;单实例测试&lt;/h2&gt;

&lt;p&gt;单实例测试很容易，使用 Kubernetes 的 Job 方式即可：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: jmeter
spec:
  template:
    metadata:
      name: jmeter
    spec:
      restartPolicy: Never
      containers:
      - name: jmeter
        image: dustise/jmeter-server
        command:
          - &amp;quot;/jmeter/bin/jmeter&amp;quot;
          - &amp;quot;-n&amp;quot;
          - &amp;quot;-t&amp;quot;
          - &amp;quot;/jmeter/input/jd.jmx&amp;quot;
          - &amp;quot;-l&amp;quot;
          - &amp;quot;/jmeter/output/log&amp;quot;
          - &amp;quot;-j&amp;quot;
          - &amp;quot;/jmeter/output/joker&amp;quot;
        volumeMounts:
        - name: data
          mountPath: /jmeter/input
          subPath: input
        - name: data
          mountPath: /jmeter/output
          subPath: output
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: jmeter-task
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的定义中：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;任务 Pod 加载了存储卷 &lt;code&gt;jmeter-task&lt;/code&gt;。使用 &lt;code&gt;subPath&lt;/code&gt; 指令，分别挂载了输入和输出目录。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;使用 &lt;code&gt;-n -t&lt;/code&gt; 的方式运行测试任务，并把输出文件定位到 &lt;code&gt;output&lt;/code&gt; 目录中。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接下来就可以使用 &lt;code&gt;kubectl create -f jobs1.yaml&lt;/code&gt; 来运行这一任务。&lt;/p&gt;

&lt;p&gt;任务启动之后，可以：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;使用 &lt;code&gt;kubectl get jobs&lt;/code&gt; 来查看任务运行状况。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;kubectl get pods --show-all&lt;/code&gt; 查看任务 Pod。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;kubectl logs -f [pod name&lt;/code&gt; 查看任务输出。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后任务会变成完成状态，就可以在 Web DAV 中查看任务报告了。&lt;/p&gt;

&lt;h2 id=&#34;集群测试&#34;&gt;集群测试&lt;/h2&gt;

&lt;p&gt;Jmeter 可以使用控制台+负载机的形式，使用多个节点进行压力测试，这里需要解决的一个最重要问题就是，在指派任务给负载机时，Jmeter 需要使用 &lt;code&gt;-R host:port&lt;/code&gt; 的参数，来指定任务要调用的负载机。这一通信是无法通过 Kubernetes 方式的 Service 来完成的。必须建立 Pod 之间的通信，而 Pod 的主机名地址是很飘逸的，同时，我们还是希望负载节点的数量能够实现较为自由的伸缩，因此解决方法就只有 StatefulSet 了。&lt;/p&gt;

&lt;p&gt;这个 YAML 很长，所以放在最后了，说说其中的要点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;注解中的 &lt;code&gt;security.alpha.kubernetes.io/sysctls&lt;/code&gt;：实际运行中，jmeter 负载机是需要对内核参数进行一点调整的，Pod 中可以用这一方式进行调整，&lt;code&gt;https://kubernetes.io/docs/concepts/cluster-administration/sysctl-cluster/&lt;/code&gt; 中有更详细的关于这方面的内容讲解。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;spec.affinity&lt;/code&gt;：这里设置 Jmeter Pod 尽量分布在不同节点上。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;RMI_HOST&lt;/code&gt;环境变量：使用每个 Pod 的 IP 为这一变量赋值。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Service：利用这个 Headless 服务，为每个 Pod 提供主机名支持。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;启动这个 Statefulset 之后，会看到规律创建的 Pod 名称：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;jnode-0                   1/1       Running   0          1h
jnode-1                   1/1       Running   0          1h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对应的主机名称就应该是 jnode-0.jfarm，jnode-1.jfarm。所以上面的 &lt;code&gt;job.yaml&lt;/code&gt; 可以新增 &lt;code&gt;-R jnode-0.jfarm:1099,jnode-1.jfarm:1099&lt;/code&gt; 即可。&lt;/p&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl create&lt;/code&gt; 启动任务之后，查看该任务 Pod 的日志，会出现大致这样的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Creating summariser &amp;lt;summary&amp;gt;
Created the tree successfully using /jmeter/input/jd.jmx
Configuring remote engine: jnode-0.jfarm:1099
Configuring remote engine: jnode-1.jfarm:1099
Starting remote engines
Starting the test @ Thu Nov 16 07:24:14 GMT 2017 (1510817054558)
Remote engines have been started
Waiting for possible Shutdown/StopTestNow/Heapdump message on port 4445
summary +    302 in 00:01:16 =    4.0/s Avg:  2967 Min:  2627 Max:  5457 Err:     0 (0.00%) Active: 0 Started: 20 Finished: 20
summary +     98 in 00:00:00 = 3062.5/s Avg:  3270 Min:  2635 Max:  7192 Err:     0 (0.00%) Active: 0 Started: 20 Finished: 20
summary =    400 in 00:01:16 =    5.3/s Avg:  3041 Min:  2627 Max:  7192 Err:     0 (0.00%)
Tidying up remote @ Thu Nov 16 07:25:31 GMT 2017 (1510817131966)
... end of run
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，成功配置远程负载服务器之后，测试开始，最后成功完成。&lt;/p&gt;

&lt;h2 id=&#34;statefulset-源码&#34;&gt;Statefulset 源码&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: jnode
  labels:
    app: jmeter
    component: node
spec:
  serviceName: jfarm
  replicas: 2
  selector:
    matchLabels:
      app: jmeter
      component: node
  template:
    metadata:
      labels:
        app: jmeter
        component: node
      annotations:
        security.alpha.kubernetes.io/sysctls: net.ipv4.ip_local_port_range=10000 65000,net.ipv4.tcp_syncookies=1
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              topologyKey: kubernetes.io/hostname
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - jmeter
                - key: component
                  operator: In
                  values:
                  - node
      restartPolicy: Always
      containers:
      - name: jmeter
        image: dustise/jmeter-server
        ports:
        - name: server
          containerPort: 1099
        - name: rmi
          containerPort: 20000
        env:
        - name: RMI_HOST
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
---
apiVersion: v1
kind: Service
metadata:
  name: jfarm
  labels:
    app: jmeter
spec:
  clusterIP: None
  ports:
  - port: 1099
    name: server
  selector:
    app: jmeter
    component: node
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>CI/CD 工具链的分分合合</title>
      <link>/post/cicd-toolchain-or-toolset/</link>
      <pubDate>Fri, 03 Nov 2017 04:49:29 +0800</pubDate>
      <guid>/post/cicd-toolchain-or-toolset/</guid>
      <description>

&lt;h2 id=&#34;作案动机&#34;&gt;作案动机&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;一种对 ci/cd 工具的轻量化和解耦的尝试？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jenkins 的传统集群方式，是使用不同环境的服务器构成不同能力的 Jenkins 节点，由主节点根据任务
环节的需要，调度不同能力的子节点来完成构建或部署任务。&lt;/p&gt;

&lt;p&gt;进入容器云时代，情况发生了变化，我们可以使用不同能力的 Jenkins 镜像，使用 Kubernetes 插件来
完成这种任务的拆分和调度，为此，我构建了一个包含所有我们平时用到的工具的 Jenkins 镜像，简化了
节点的扩展和选择过程。&lt;/p&gt;

&lt;p&gt;然而随着学习和应用的深入，我意识到这种做法有几个问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DevOps 中隐含着发挥个人能力的愿景，工具链的所谓大而全，只不过是在画一个比较大的圈，使用这样的一套 Jenkins，还是要被其中所包含的仅有的工具中进行选择，对身陷其中的技术人员绝不能说是友好，也绝不是鼓励各展所长的态度。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;现有的功能测试、接口测试、压力测试等工具，越来越专业化，往往会有各自的工作集群调度甚至是托管方案，例如 selenium grid、JMeter 集群等。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;同样的测试工作，可能有多种工具都可以完成，例如一个 Restful 的接口测试，不管是 JMeter 还是Postman，或者 SoapUI 以及五花八门的自有工具，都可以完成这样的工作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;各种 DevOps 以及微服务管控和治理平台会有各自的工具链构成以及扩展方案。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;让各种工具自成镜像，无疑对镜像尺寸和更新速度都会有更好的支持。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样就让我产生一个新的思路：能不能让 Jenkins 回归到一个原始状态，只负责问题的定义、任务的分发和结果的归集呢？于是就有了这样的一点尝试。&lt;/p&gt;

&lt;p&gt;这一尝试的思路是，Jenkins 镜像/容器只使用插件和一些 Shell 脚本，同外部的调度能力（例如
Docker 的容器或者 Kubernetes 的任务等）进行交互，利用网络和共享存储，来实现任务的分发和协调
以及最后的结果汇聚。&lt;/p&gt;

&lt;p&gt;下面以 JMeter 为例，进行一个简单的压力测试，测试环境为了节省起见，使用的是 Docker 加本地目录
共享的方式，这种方式也可以很方便的扩展为 Kubernetes 的 Job + PV/PVC 方式。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Jenkins: dustise/jenkins&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;JMeter: hauptmedia/jmeter&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;注意此处随意的选择了一个 2.x 版本的 JMeter。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;jenkins-插件&#34;&gt;Jenkins 插件&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker_build_step&lt;/code&gt;：用于执行 Docker 指令，类似功能的 Docker Common 插件，其参数不支持环境变量，因此淘汰。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Performance Plugin：一个用于搜集和展示多种测试结果报告的插件。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;存储&#34;&gt;存储&lt;/h2&gt;

&lt;p&gt;这里我们需要为 Jenkins 和各种工具（这里是 JMeter）提供一个可以共享访问的文件交换区域：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;单机 Docker 下，可以是共享的主机目录。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Kubernetes 环境可以使用共享的分布式存储卷。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如我们创建名为&lt;code&gt;/var/cicd/exchange/jmeter&lt;/code&gt;的目录，进行文件交换。&lt;/p&gt;

&lt;h2 id=&#34;输入&#34;&gt;输入&lt;/h2&gt;

&lt;p&gt;使用 JMeter GUI 录制 jmx 文件，为任务生成汇总日志（保存到/root/summary.log）。
将 jmx 上传到上面所说的目录之中，这样就保证了 Docker 和 Jenkins 都能通过 &lt;code&gt;-v&lt;/code&gt; 来进行加载。&lt;/p&gt;

&lt;p&gt;这里我们启动 Jenkins 的时候，使用 &lt;code&gt;-v /var/cicd/exchange/jmeter:/exchange/jmeter&lt;/code&gt;
参数让 Jenkins 加载这一目录。&lt;/p&gt;

&lt;h2 id=&#34;jenkins-任务&#34;&gt;Jenkins 任务&lt;/h2&gt;

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

&lt;p&gt;首先的环节是，为后面要开工的 JMeter 准备工作环境。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 创建当前 Build 目录
mkdir -p /exchange/jmeter/$BUID_TAG
# 复制 jmx 到当前 Build 目录
cp /exchange/jmeter/jd.jmx /exchange/jmeter/$BUID_TAG
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;设置容器&#34;&gt;设置容器&lt;/h3&gt;

&lt;p&gt;准备好文件之后，我们需要添加下一个环节就是设置一个容器：&lt;/p&gt;

&lt;p&gt;这里添加的环节是&lt;code&gt;docker_build_step&lt;/code&gt;的&lt;code&gt;Execute Docker Command&lt;/code&gt;环节。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Image name: hauptmedia/jmeter&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Command：bin/jmeter -n -t /root/jd2.jmx -l /root/result.log -j /root/process.log&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Bind Mounts：&lt;code&gt;/var/cicd/kube/$BUILD_TAG /root&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;注意绑定卷这里就使用了 $BUILD_TAG 变量&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这样就完成了 Jenkins 和 JMeter 的文件共享：&lt;/p&gt;

&lt;p&gt;Jenkins 的 &lt;code&gt;/exchange/jmeter/$BUILD_TAG&lt;/code&gt;，对应的是新创建容器的&lt;code&gt;/root&lt;/code&gt;目录。&lt;/p&gt;

&lt;h3 id=&#34;启动容器&#34;&gt;启动容器&lt;/h3&gt;

&lt;p&gt;使用&lt;code&gt;Execute Docker Command&lt;/code&gt;的&lt;code&gt;start container(s)&lt;/code&gt;环节，&lt;code&gt;Container ID(s)&lt;/code&gt;填写变量
&lt;code&gt;$DOCKER_CONTAINER_IDS&lt;/code&gt;，代表启动我们刚才创建的容器。&lt;/p&gt;

&lt;h3 id=&#34;等待任务&#34;&gt;等待任务&lt;/h3&gt;

&lt;p&gt;如果我们需要使用 Jenkins 进行结果的汇聚，那么这里就需要进行阻塞——等到 JMeter 执行完毕后，
才能进行下面的搜集结果、清理现场等操作。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;RESULT=&amp;quot;/exchange/jmeter/$BUILD_TAG/summary.log&amp;quot;
while [ ! -f $RESULT ]
do
  sleep 30
done


while [[ `lsof | grep summary.log` ]]
do
    sleep 10
done
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的脚本利用 lsof 来检测 summary.log 的占用情况，一旦该文件关闭，说明压力测试已经结束。&lt;/p&gt;

&lt;h3 id=&#34;清理容器&#34;&gt;清理容器&lt;/h3&gt;

&lt;p&gt;使用 &lt;code&gt;Execute Docker Command&lt;/code&gt; 的 &lt;code&gt;remove container(s)&lt;/code&gt; 环节，&lt;code&gt;Container ID(s)&lt;/code&gt; 填写变量
&lt;code&gt;$DOCKER_CONTAINER_IDS&lt;/code&gt;，代表清理我们刚才创建的容器。&lt;/p&gt;

&lt;h3 id=&#34;结果展示&#34;&gt;结果展示&lt;/h3&gt;

&lt;p&gt;Performance Plugin 能够识别 JMeter、SoapUI 以及 Parrot 生成的报告文件，这里我们只设置一
个选项，就是 &lt;code&gt;Source data files (autodetects format):&lt;/code&gt;，这里填写
&lt;code&gt;/exchange/jmeter/$BUILD_TAG/summary.log&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;插件会把这一文件拷贝到 Workspace，进行解析和显示。&lt;/p&gt;

&lt;h2 id=&#34;kubernetes-job&#34;&gt;Kubernetes Job&lt;/h2&gt;

&lt;p&gt;上述过程可以很方便的改造成为 Kubernetes Job：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker 相关内容，改为使用 kubectl 进行的 job 文件的生成和操作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;阻塞过程可以查询任务节点的状态&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;文件存储可以使用 PVC 来共享&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>K8S 上 Elastic Search 集群的通信加密、认证和清理</title>
      <link>/post/es-on-k8s-auth-cleanup/</link>
      <pubDate>Fri, 03 Nov 2017 04:49:29 +0800</pubDate>
      <guid>/post/es-on-k8s-auth-cleanup/</guid>
      <description>

&lt;p&gt;Kubernetes 的 Release 文件包中，一直包含了使用 Elastic Search 方案进行日志处理的简单例子，
这个例子非常简陋外加版本较旧，处于“能用”的状态而已。&lt;/p&gt;

&lt;p&gt;而近期的版本中这一情况发生了变化，原来的 &lt;code&gt;elasticsearch&lt;/code&gt; 中新增了一个子目录：
&lt;code&gt;production_cluster&lt;/code&gt;，&lt;code&gt;README.md&lt;/code&gt;中的介绍是：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A more robust example that follows Elasticsearch best-practices of separating nodes concern is also available.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这个听起来就厉害了，关键字：robuts，best-practice。&lt;/p&gt;

&lt;p&gt;顺藤摸瓜找到了作者的 github 地址：&lt;code&gt;https://github.com/pires/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这一集群的特点是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ES 集群分为 Master/Client/Data 三组，各司其职，各自可以设置自己的资源使用，参数配置等。&lt;/li&gt;
&lt;li&gt;提供了 StatefulSet 形式的数据节点，便于数据持久化的支持。&lt;/li&gt;
&lt;li&gt;采用 Curator 的 CronJob，用于数据的清理。&lt;/li&gt;
&lt;li&gt;自定义的 Elastic Search 镜像。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这一些功能自然是极好的，然而因为 X-Pack 的授权问题，使得两个重要的功能： https 通信和认证落了空，还好发现了一个替代方案：&lt;a href=&#34;https://github.com/floragunncom/search-guard&#34; target=&#34;_blank&#34;&gt;Search Guard&lt;/a&gt;，
简单说来，这一方案提供了免费的认证和 https 通信方案，并且提供了更多的商业支持特性。具体能力范围可以参看官网，下面基于 Pries 的 ES 5.6.3 版本，来把假设在 Kubernetes 集群上的 ES 集群进行加固。&lt;/p&gt;

&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 集群：1.7.x

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kube-apiserver&lt;/code&gt;启动参数中加入&lt;code&gt;--runtime-config=batch/v2alpha1=true&lt;/code&gt;用于支持后面的 CronJob 对象&lt;/li&gt;
&lt;li&gt;集群存储，用于满足 PVC 需求（可选）&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Docker：用于自定义镜像的构建&lt;/li&gt;
&lt;li&gt;源代码：

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/pires/kubernetes-elasticsearch-cluster&#34; target=&#34;_blank&#34;&gt;https://github.com/pires/kubernetes-elasticsearch-cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/fluent/fluentd-kubernetes-daemonset.git&#34; target=&#34;_blank&#34;&gt;https://github.com/fluent/fluentd-kubernetes-daemonset.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Search Guard 的安装分为插件安装、初始化和集群设置三个步骤，Pries 镜像中推荐的插件安装方式仅能完成第一步骤，因此这里做一些定制，将前两个步骤在镜像中直接完成。&lt;/p&gt;

&lt;p&gt;这里我们使用 Pries 镜像为基础：&lt;/p&gt;

&lt;h3 id=&#34;dockerfile&#34;&gt;Dockerfile&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM quay.io/pires/docker-elasticsearch-kubernetes:5.6.3
COPY prepare.sh /tmp
RUN sh /tmp/prepare.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;prepare-sh&#34;&gt;prepare.sh&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
set -xe
export NODE_NAME=&amp;quot;MASTER&amp;quot; # 占位符
# 插件安装
bin/elasticsearch-plugin install -b com.floragunn:search-guard-5:5.6.3-16

# 初始化
chmod a+x plugins/search-guard-5/tools/install_demo_configuration.sh
plugins/search-guard-5/tools/install_demo_configuration.sh -y

# 清理不必要的配置
sed -i  &#39;s/network.host.*0$//&#39; config/elasticsearch.yml
sed -i  &#39;s/cluster.name.*demo$//&#39; config/elasticsearch.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;运行-es-集群&#34;&gt;运行 ES 集群&lt;/h2&gt;

&lt;p&gt;运行集群之前，注意三处需要修改：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;镜像名称。&lt;/li&gt;

&lt;li&gt;&lt;p&gt;环境变量加入：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- name: &amp;quot;NETWORK_HOST&amp;quot;
value: &amp;quot;_eth0_&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;因为 Search Guard 的加入，Client 的可用检测是失效的，因此需要删除。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl apply -f es-discovery-svc.yaml
kubectl apply -f es-svc.yaml
kubectl apply -f es-master.yaml
kubectl apply -f es-client.yaml
kubectl apply -f es-data.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;集群启动之后会处于不可用状态，需要进行 Search guard 设置，使用 kubectl 命令进入任意一个 Master 节点的 Shell，编辑如下文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/bash
/elasticsearch/plugins/search-guard-5/tools/sgadmin.sh \
-cd /elasticsearch/plugins/search-guard-5/sgconfig \
-ks /elasticsearch/config/kirk.jks  -arc \
-ts /elasticsearch/config/truststore.jks -nhnv -icl \
-h 172.200.62.7
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-icl&lt;/code&gt; 参数用于禁止证书 CN 的检查。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-h&lt;/code&gt; 指定该 Pod 的地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-arc&lt;/code&gt; 接受状态为 RED 的集群操作。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样就完成了 ES 集群的初始化设置，并且开始运行。
这时我们如果访问其服务，例如：&lt;code&gt;https://node.port:9200&lt;/code&gt;，如果弹出安全警告，在选择不检查证书之后，会弹出验证窗口，输入预置的：&lt;code&gt;admin/admin&lt;/code&gt; 就能看到正常的 API 页面了。&lt;/p&gt;

&lt;h2 id=&#34;fluentd&#34;&gt;Fluentd&lt;/h2&gt;

&lt;p&gt;因为我们的 Fluentd 需要访问 https 协议的有认证要求的 ES 集群，所以这里使用 ConfigMap 的方式，为 Fluentd 加载修改好的配置文件。&lt;/p&gt;

&lt;p&gt;首先使用 &lt;code&gt;docker cp&lt;/code&gt; 命令，或者直接从源码中获取 fluent.conf 和 kubernetes.conf 两个文件。&lt;/p&gt;

&lt;p&gt;在 fluent.conf 的 es 配置中加入 &lt;code&gt;ssl_verify false&lt;/code&gt; 一行。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--from-file&lt;/code&gt; 开关将上文中的两个配置文件加入 ConfigMap。&lt;/p&gt;

&lt;p&gt;修改 DaemonSet 的源码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
# 接入配置
- name: FLUENT_ELASTICSEARCH_SCHEME
  value: &amp;quot;https&amp;quot;
- name: FLUENT_ELASTICSEARCH_USER
  value: &amp;quot;admin&amp;quot;
- name: FLUENT_ELASTICSEARCH_PASSWORD
  value: &amp;quot;admin&amp;quot;
...
# 配置文件

volumeMounts:
...
- name: etc
  mountPath: /fluentd/etc
terminationGracePeriodSeconds: 30
volumes:
...
- name: etc
configMap:
  name: fluentd-config
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样，Fluentd 就能成功连接 es 并发送日志了。&lt;/p&gt;

&lt;h2 id=&#34;kibana&#34;&gt;Kibana&lt;/h2&gt;

&lt;p&gt;和 Fluentd 的情况类似，也需要创建他的配置文件，并在 kibana.yml 原有内容基础上增加几行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;elasticsearch.username: &amp;quot;admin&amp;quot;
elasticsearch.password: &amp;quot;admin&amp;quot;
elasticsearch.ssl.verificationMode: none
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;另外在他的 Deployment 描述中，需要将 ES 集群接口地址改为 https 协议。&lt;/p&gt;

&lt;p&gt;启用后，打开 Kibana 页面，就会弹出认证要求。&lt;/p&gt;

&lt;h2 id=&#34;curator&#34;&gt;Curator&lt;/h2&gt;

&lt;p&gt;编辑 es-curator-config.yml，修改：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;use_ssl: True
ssl_no_validate: True
http_auth: admin:admin
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后创建 ConfigMap 和 Cronjob 对象即可。&lt;/p&gt;

&lt;h2 id=&#34;补充&#34;&gt;补充&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;sgadmin 及其配置有着相当丰富的功能，例如用户和角色的管理等。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;证书也是可以使用合法证书进行替代的，不一定需要使用自这个过程中生成的自签名证书。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes &#43; Blackbox 实现对 Web 和 DNS 的简单监控</title>
      <link>/post/blackbox-monitor-dns-web/</link>
      <pubDate>Tue, 19 Sep 2017 23:26:45 +0800</pubDate>
      <guid>/post/blackbox-monitor-dns-web/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;其实都在这里了：
&lt;a href=&#34;https://github.com/prometheus/blackbox_exporter/blob/master/CONFIGURATION.md&#34; target=&#34;_blank&#34;&gt;https://github.com/prometheus/blackbox_exporter/blob/master/CONFIGURATION.md&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Prometheus 带有很多有针对性的 Exporter，能够对 MySQL、Apache 或者 ElasticSearch 等服务
器进行监控，另外还有 Blackbox Exporter 用于对 http dns tcp 等零散目标进行简单监控。&lt;/p&gt;

&lt;h2 id=&#34;dns-的监控&#34;&gt;DNS 的监控&lt;/h2&gt;

&lt;p&gt;首先需要运行一个 Blackbox 的 Deployment，并利用 Configmap 来为 Blackbox 提供配置文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
data:
  config.yml: |
    modules:
      http_2xx:  # http 检测模块
        prober: http
        http:
      http_post_2xx: # http post 模块
        prober: http
        http:
          method: POST
      tcp_connect: # tcp 检测模块
        prober: tcp
      dns:  # dns 检测模块
        prober: dns
        dns:
          transport_protocol: &amp;quot;tcp&amp;quot;
          preferred_ip_protocol: &amp;quot;ip4&amp;quot;
          query_name: &amp;quot;kubernetes.default.svc.cloud.ctrm&amp;quot;  # 利用这个域名来检查 dns 服务器
          query_type: &amp;quot;A&amp;quot;  # 如果是 kube-dns ，一定要加入这个
kind: ConfigMap
metadata:
  name: blackbox
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: blackbox
spec:
  template:
    metadata:
      labels:
        name: blackbox
    spec:
      containers:
      - image: prom/blackbox-exporter:v0.8.1
        name: blackbox
        ports:
        - containerPort: 9115
        volumeMounts:
        - name: config
          mountPath: /etc/blackbox_exporter
        args:
        - --config.file=/etc/blackbox_exporter/config.yml # Configmap 中的配置文件
        - --log.level=error  # 错误级别控制
      volumes:
      - name: config
        configMap:
          name: blackbox
---
apiVersion: v1
kind: Service
metadata:
  name: blackbox
spec:
  selector:
    name: blackbox
  ports:
  - port: 80
    targetPort: 9115
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 &lt;code&gt;kubectl apply&lt;/code&gt; 命令运行起来。&lt;/p&gt;

&lt;p&gt;接下来需要在 Prometheus 的配置文件中加入对 BlackBox 的抓取设置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- job_name: &amp;quot;kubernetes-service-dns&amp;quot;
  metrics_path: /probe # 不是 metrics，是 probe
  params:
    module: [dns] # DNS 模块
  static_configs:
  - targets:
    - kube-dns:53 # 不要省略端口号
  relabel_configs:
  - source_labels: [__address__]
    target_label: __param_target
  - source_labels: [__param_target]
    target_label: instance
  - target_label: __address__
    replacement: blackbox # 服务地址，和上面的 Service 定义保持一致
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;重启 Prometheus，或者利用 curl Post 更新配置。打开 Prometheus 的 Target 页面，就会看到
上面定义的 &lt;code&gt;kubernetes-service-dns&lt;/code&gt; 任务了，回到 Graph 页面，可以使用
&lt;code&gt;probe_success{job=&amp;quot;kubernetes-service-dns&amp;quot;}&lt;/code&gt; 来查看检测历史结果。&lt;/p&gt;

&lt;h2 id=&#34;http-监控&#34;&gt;HTTP 监控&lt;/h2&gt;

&lt;p&gt;上面的配置文件中提到有一个 &lt;code&gt;http_2xx&lt;/code&gt; 的模块，这里我们可以使用他对 http 服务进行检测。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这里主要是对不受我们控制的外部服务的快速检测。
内部的方法就丰富多了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;因为前面已经给 Blackbox 配置了 http_2xx 模块，所以这里只需要在 Prometheus 中加入抓取任务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- job_name: &amp;quot;business-service-liveness&amp;quot;
  metrics_path: /probe
  params:
    module: [http_2xx]
  static_configs:
  - targets:
    - http://192.168.51.129:30001 # 要检查的网址
    - http://192.168.51.129:30004
    - http://192.168.51.129:30003
  relabel_configs:
  - source_labels: [__address__]
    target_label: __param_target
  - source_labels: [__param_target]
    target_label: instance
  - target_label: __address__
    replacement: blackbox-exporter:9115
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;同样的，重新载入之后，可以使用 &lt;code&gt;probe_success&lt;/code&gt; 和 &lt;code&gt;probe_duration_seconds&lt;/code&gt; 等来检查历史结果。&lt;/p&gt;

&lt;h2 id=&#34;自带-metrics-端点的服务&#34;&gt;自带 metrics 端点的服务&lt;/h2&gt;

&lt;p&gt;有的服务，例如 &lt;code&gt;prometheus&lt;/code&gt; 或者 &lt;code&gt;blackbox&lt;/code&gt;，以及 &lt;code&gt;kube-dns&lt;/code&gt;、&lt;code&gt;etcd&lt;/code&gt; 等，
都是自有 &lt;code&gt;/metrics&lt;/code&gt; 提供指标输出的，这种服务对 Blackbox + Prometheus 组合是非常方便的。&lt;/p&gt;

&lt;p&gt;只要给服务的注解部分加入几个标签：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;prometheus.io/host: calico-etcd # 服务名称
prometheus.io/port: &amp;quot;6666&amp;quot; # metrics 端口
prometheus.io/scrape: &amp;quot;true&amp;quot; # 抓取开关
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面是 calico 的 etcd 服务加入的注解，服务中有了上述注解之后，Prometheus 的示例配置中，已经
有了针对这一配置的监控方法，直接刷新 Target 页面，就会看到新的监控目标，可以进行使用了。而无需
为每个服务分别定制 Target。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml&#34; target=&#34;_blank&#34;&gt;https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 应用故障的一些定位方法</title>
      <link>/post/debug-in-kubernetes/</link>
      <pubDate>Tue, 05 Sep 2017 06:03:04 +0800</pubDate>
      <guid>/post/debug-in-kubernetes/</guid>
      <description>

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

&lt;h3 id=&#34;准备一个工具镜像&#34;&gt;准备一个工具镜像&lt;/h3&gt;

&lt;p&gt;其中包含 nslookup, ping, curl, 甚至是 ab、siege 等常用工具以及一个顺手的 Shell。一言不合就可以用静态 Pod 的方式将其运行到 Kubernetes 之中进行内部诊断。&lt;/p&gt;

&lt;h3 id=&#34;sysctl-a-grep-forwarding&#34;&gt;sysctl -a | grep forwarding&lt;/h3&gt;

&lt;p&gt;你猜这是干啥的？&lt;/p&gt;

&lt;h3 id=&#34;服务状态查询&#34;&gt;服务状态查询&lt;/h3&gt;

&lt;p&gt;各个 Kubernetes 组件的状态检查。可以使用 Ansible 之类的工具进行快速查询。&lt;/p&gt;

&lt;h2 id=&#34;service-不通&#34;&gt;Service 不通&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;这里我们首先假设 Pod 工作正常&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;目前我们的应用均采用的是 NodePort 模式对外提供服务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;逻辑&lt;/strong&gt;：Service 将 &lt;strong&gt;符合其选择器的 Pod 暴露的端口&lt;/strong&gt; 从 &lt;strong&gt;各个 Node&lt;/strong&gt; 的同一端口暴露出来对外进行监听。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;技术&lt;/strong&gt;：&lt;strong&gt;Kube-proxy&lt;/strong&gt; 通过网络插件，一般利用 Iptables vxLan 等乌七八糟的蜜汁技术，完成对外服务负载均衡，并分发给各个 Pod 的内部 IP 的相应端口。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;前面我们假设 Pod 是正常工作的，因此，这里只考虑 Service 的情况。&lt;/p&gt;

&lt;p&gt;通过上面的陈述我们能看到大致的一些要素，下面从内向外进行列表：&lt;/p&gt;

&lt;h3 id=&#34;pod-能够正常工作&#34;&gt;Pod 能够正常工作&lt;/h3&gt;

&lt;p&gt;见后文&lt;/p&gt;

&lt;h3 id=&#34;service-的选择器能够正确的找到-pod&#34;&gt;Service 的选择器能够正确的找到 Pod&lt;/h3&gt;

&lt;p&gt;这里我们可以使用&lt;code&gt;kubectl describe svc panic-service&lt;/code&gt;命令，查看输出内容的&lt;code&gt;endpoint&lt;/code&gt;一节内容，如果其中有 Pod 地址，也就说明选择器和 Pod 的标签是匹配的。如果为空，则需要对服务或者 Pod Controller 的定义进行排查。&lt;/p&gt;

&lt;h3 id=&#34;proxy-的工作状态&#34;&gt;Proxy 的工作状态&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;首先可以使用&lt;code&gt;systemctl -l Kube-proxy&lt;/code&gt;来查看服务状况。&lt;/li&gt;
&lt;li&gt;还可以使用其他 Node 的同一端口测试访问，看是否单一节点的故障。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;dns-工作状态&#34;&gt;DNS 工作状态&lt;/h3&gt;

&lt;p&gt;Kubectl 查看 DNS 各个 Pod 的存活状态。&lt;/p&gt;

&lt;p&gt;利用上面提到的工具 Pod 尝试解析服务。失败了其实也没啥办法，删 DNS Pod 重启吧。&lt;/p&gt;

&lt;h3 id=&#34;端口是否定义正确&#34;&gt;端口是否定义正确&lt;/h3&gt;

&lt;p&gt;看 Pod 的端口是否能够正确侦听，是否符合服务定义。例如 Service 定义了到 Pod 8080 端口的访问，而 Pod 开放的却是 80，这样的情况跟标签无法匹配一样，是很常见的问题。&lt;/p&gt;

&lt;h2 id=&#34;说完了服务-我们来说说-pod&#34;&gt;说完了服务，我们来说说 Pod&lt;/h2&gt;

&lt;h3 id=&#34;两个顺手的命令&#34;&gt;两个顺手的命令：&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kubectl get po -o wide | grep -v Running&lt;/code&gt;
 &lt;code&gt;kubectl describe po unhealthy&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;一般来说，一个行为端正的 Pod，应该是以 Running 状态持续运行的。在进入 Running 之前，大致有调度、创建、初始化等几个环节，如果正常运行之后出了故障，会发生重启。如果在启动容器内进程时出现问题，则会进入 CrashLoopBackOff 的状态。&lt;/p&gt;

&lt;h3 id=&#34;除了-running-complet-以及-crashloopbackoff&#34;&gt;除了 Running/Complet 以及 CrashLoopBackOff：&lt;/h3&gt;

&lt;p&gt;这几种情况其实不同，不过随性写到这，就不深究了，首先是 describe 一下。&lt;/p&gt;

&lt;p&gt;Pod 启动有几个条件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;有符合要求的节点供其运行

&lt;ul&gt;
&lt;li&gt;Taint 隔离的节点，要求 Pod 有显式声明对该种 Taint 的容错能力，才可以在其上运行。&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;节点和 Pod 的亲和性定义&lt;/li&gt;
&lt;li&gt;Node Selector 的定义&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;符合其需求的资源

&lt;ul&gt;
&lt;li&gt;CPU 和 内存的 request limit 定义&lt;/li&gt;
&lt;li&gt;可能存在的第三方资源需求定义&lt;/li&gt;
&lt;li&gt;加载卷（nfs gluster ceph 等）/Secret/Configmap 的定义&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;镜像必须存在，可 Pull&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;调度部分一般来说查看 Pod 定义，和节点的 Describe 进行匹配即可，Describe 内容中也会明确说出无合适 Pod。&lt;/p&gt;

&lt;p&gt;资源部分 CPU 和内存的 Describe 结果也会很明显。&lt;/p&gt;

&lt;p&gt;存储部分，往往就需要更复杂的排查：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;首先看看是不是每个 Node 都如此。&lt;/li&gt;
&lt;li&gt;是否安装了对应的客户端驱动。&lt;/li&gt;
&lt;li&gt;对分布式存储的访问网络是否可用。&lt;/li&gt;
&lt;li&gt;存储服务容量是否足够分配。&lt;/li&gt;
&lt;li&gt;是否能够成功的手工 Mount。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;至于对 ConfigMap 和 Secret 的依赖，很简单，Kubectl 查询即可。&lt;/p&gt;

&lt;h3 id=&#34;crashloopbackoff-以及-restart-大于-1&#34;&gt;CrashLoopBackOff 以及 Restart 大于 1&lt;/h3&gt;

&lt;p&gt;这种情况一般来说属于业务内部的问题，可以通过 &lt;code&gt;kubectl logs -f ...&lt;/code&gt;
命令进行查看，目前经验比较多的非业务情况是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;对于 Kubernetes API 进行访问的应用，经常会是因为 RBAC 权限不足导致无法启动&lt;/li&gt;
&lt;li&gt;依赖的 Service 无法访问。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 的审计日志和采集</title>
      <link>/post/auditlog-in-k8s/</link>
      <pubDate>Thu, 31 Aug 2017 01:35:22 +0800</pubDate>
      <guid>/post/auditlog-in-k8s/</guid>
      <description>

&lt;h2 id=&#34;基础操作&#34;&gt;基础操作&lt;/h2&gt;

&lt;p&gt;一个正常运行的 Kubernetes 集群，除了利用访问控制对集群操作的许可进行限制之外，对于操作过程的跟踪审计也是比不可少的，围绕不同的实体，例如用户、节点以及各种工作负载进行观测是很有必要的。Kubernetes 的 API Server 提供了审计日志支持，利用审计日志的方式对系统内的操作进行记录，这里我们可以沿用推荐的 Elastic Search + Fluentd 对审计日志进行采集存储，最终使用 Kibana 或者其他支持 ES 查询的工具对关键资源或用户进行访问跟踪。&lt;/p&gt;

&lt;p&gt;首先要启用 API Server 的审计功能。Kubernetes 提供了四个基础参数来定义审计功能：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;audit-log-path&lt;/strong&gt; 启用审计日志，并将日志内容写入指定文件，“-” 代表 stdout。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;audit-log-maxage&lt;/strong&gt; 日志文件的最大保存天数，根据文件名中的日期进行确定。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;audit-log-maxbackup&lt;/strong&gt; 最多保存日志文件的数量。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;audit-log-maxsize&lt;/strong&gt; 最大文件尺寸，超过尺寸会被翻转。单位是 MB，缺省为 100MB。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;--audit-log-path=/var/log/kubernetes/kubernetes.audit \
--audit-log-maxage=7 \
--audit-log-maxbackup=4 \
--audit-log-maxsize=10
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在 Kubernetes API Server 的启动参数中加入这些开关之后，重新启动服务。&lt;/p&gt;

&lt;p&gt;这时我们就可以看到文件&lt;code&gt;/var/log/kubernetes/kubernetes.audit&lt;/code&gt;已经生成。利用 tail 命令看看他的结构和内容，例如请求内容是这样的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;2017-08-30T16:28:35.485818099+08:00 AUDIT: id=&amp;quot;ebc47b7b-c4fe-4a9a-861c-d9686903cec4&amp;quot; ip=&amp;quot;127.0.0.1&amp;quot; method=&amp;quot;GET&amp;quot; user=&amp;quot;system:apiserver&amp;quot; groups=&amp;quot;\&amp;quot;system:masters\&amp;quot;&amp;quot; as=&amp;quot;&amp;lt;self&amp;gt;&amp;quot; asgroups=&amp;quot;&amp;lt;lookup&amp;gt;&amp;quot; namespace=&amp;quot;&amp;lt;none&amp;gt;&amp;quot; uri=&amp;quot;/apis/admissionregistration.k8s.io/v1alpha1/initializerconfigurations&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而响应内容格式如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;2017-08-30T16:28:35.486131325+08:00 AUDIT: id=&amp;quot;ebc47b7b-c4fe-4a9a-861c-d9686903cec4&amp;quot; response=&amp;quot;404&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们本文中暂时只对请求内容进行进一步解析，响应内容可以通过加入第二格式的方式进行采集。内容中的&lt;code&gt;id&lt;/code&gt;字段，可以看作是会话 id，用于连接请求和响应。&lt;/p&gt;

&lt;p&gt;根据上述文本内容，可以开始 Fluentd 文件的编写。请求和响应的内容都很规则，简单的正则表达式即可完成解析，例如我写的是这样的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;&amp;lt;source&amp;gt;
  type tail
  format /^(?&amp;lt;time&amp;gt;\d.*?)\s+(?&amp;lt;action&amp;gt;\w+).\s+id=\&amp;quot;(?&amp;lt;id&amp;gt;.*?)\&amp;quot;\s+ip=\&amp;quot;(?&amp;lt;ip&amp;gt;.*?)\&amp;quot;\s+method=\&amp;quot;(?&amp;lt;method&amp;gt;.*?)\&amp;quot;\s+user=\&amp;quot;(?&amp;lt;user&amp;gt;.*?)\&amp;quot;\s+groups=(?&amp;lt;groups&amp;gt;.*?)\s+as=\&amp;quot;(?&amp;lt;as&amp;gt;.*?)\&amp;quot;\s+asgroups=\&amp;quot;(?&amp;lt;asgroups&amp;gt;.*?)\&amp;quot;\s+namespace=\&amp;quot;(?&amp;lt;namespace&amp;gt;.*?)\&amp;quot;\s+uri=\&amp;quot;(?&amp;lt;uri&amp;gt;.*?)\&amp;quot;$/
  path /var/log/kubernetes/kubernetes.audit
  pos_file /var/log/audit.pos
  time_format %Y-%m-%dT%H:%M:%S.%N%z
  tag audit.response
&amp;lt;/source&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;将这一部分内容加入到 Fluentd 配置之中去，启动抓取。日志入库之后，我们就可以对指定用户或者资源进行查询，获知他的黑历史了，例如我们要查找用户&lt;code&gt;admin&lt;/code&gt;的操作历史：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;query&amp;quot;: {
    &amp;quot;match&amp;quot;: {
      &amp;quot;user&amp;quot;: {
        &amp;quot;query&amp;quot;: &amp;quot;admin&amp;quot;,
        &amp;quot;type&amp;quot;: &amp;quot;phrase&amp;quot;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在 Kibana 中执行查询，会看到类似内容（如果所在集群没有该用户，可以替换为 &lt;code&gt;system:apiserver&lt;/code&gt;等内置用户进行测试）：&lt;/p&gt;

&lt;p&gt;利用解析出的各个字段，可以比较清楚的看到什么人，在什么时间，对什么对象进行了什么操作。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/audit-screenshot.jpg&#34; alt=&#34;screenshot&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;高级审计&#34;&gt;高级审计&lt;/h2&gt;

&lt;p&gt;在 Kubernetes 1.7 中新增了 Advanced audit 特性（Alpha），可以对审计内容、以及后续处理进行定义。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;首先加入了审计策略的支持，可以使用行为，动作等条件进行限制，过滤掉无需考虑的审计内容。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;存储后端在日志之外，还增加了 Web Hook 的支持，可以直接将审计内容发布到指定的 Web 服务中。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 1.7 下的 Prometheus 监控</title>
      <link>/post/prometheus-in-k8s-1.7/</link>
      <pubDate>Mon, 28 Aug 2017 01:59:06 +0800</pubDate>
      <guid>/post/prometheus-in-k8s-1.7/</guid>
      <description>

&lt;p&gt;在 Kubernetes 的标准 Heapster + InfluxDB 的监控方案之外，还有一个监控工具就是 Prometheus 了，相比 InfluxDB 来说，Prometheus 有更集中的检测能力，更多的 Exporter（数据源）支持（不过好像还是打不过 Zabbix？），以及更新潮。。&lt;/p&gt;

&lt;p&gt;另外不少新的软件方案缺省开始支持 Prometheus 的数据抓取，所以，早上早填坑。下面是日前在一个 Kubernetes 1.7.3 集群中部署 Prometheus 监控遇到的两个坑，分享一下：&lt;/p&gt;

&lt;h2 id=&#34;cadvisor&#34;&gt;cAdvisor&lt;/h2&gt;

&lt;p&gt;官方示例解释如下：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is required for Kubernetes 1.7.3 and later, where cAdvisor metrics
(those whose names begin with &amp;lsquo;container_&amp;lsquo;) have been removed from the
Kubelet metrics endpoint.  This job scrapes the cAdvisor endpoint to
retrieve those metrics.&lt;/p&gt;

&lt;p&gt;In Kubernetes 1.7.0-1.7.2, these metrics are only exposed on the cAdvisor
HTTP endpoint; use &amp;ldquo;replacement: /api/v1/nodes/${1}:4194/proxy/metrics&amp;rdquo;
in that case (and ensure cAdvisor&amp;rsquo;s HTTP server hasn&amp;rsquo;t been disabled with
the &amp;ndash;cadvisor-port=0 Kubelet flag).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在 Kubernetes 1.7.3 和后续版本中，命名前缀为&lt;code&gt;container_&lt;/code&gt; 的 cAdvisor 指标被从 Kubelet
中移除，这一个 Job 从 cAdvisor 端点中抓取数据。&lt;/p&gt;

&lt;p&gt;在 Kubernetes 1.7.0 - 1.7.2 中，这些指标只暴露在 cAdvisor 的 http 端点中，需要使用
&lt;code&gt;eplacement: /api/v1/nodes/${1}:4194/proxy/metrics&lt;/code&gt;。（注意保证 cAdvisor 的 http
服务没有被&lt;code&gt;--cadvisor-port=0&lt;/code&gt;禁用）。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- job_name: &#39;kubernetes-cadvisor&#39;
  scheme: https
  tls_config:
    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
  kubernetes_sd_configs:
  - role: node
  relabel_configs:
  - action: labelmap
    regex: __meta_kubernetes_node_label_(.+)
  - target_label: __address__
    replacement: kubernetes.default.svc:443
  - source_labels: [__meta_kubernetes_node_name]
    regex: (.+)
    target_label: __metrics_path__
    replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;kube-state-metrics&#34;&gt;kube-state-metrics&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;https://github.com/kubernetes/kube-state-metrics&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;网址就能看出，这一服务和 Kubernetes 有点亲密。运行之后，能够为 Prometheus 提供大量详细指标，
而且这一服务的指标相对来说更具逻辑性，更方便从服务/应用的角度进行监控。&lt;/p&gt;

&lt;p&gt;目前容器地址为(不过能 pull 到最新的 v1.0.0，可能是 README 没有同步)：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gcr.io/google_containers/kube-state-metrics:v0.5.0&lt;/code&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 的证书认证</title>
      <link>/post/certs-in-kubernetes/</link>
      <pubDate>Wed, 16 Aug 2017 04:28:26 +0800</pubDate>
      <guid>/post/certs-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://jvns.ca/blog/2017/08/05/how-kubernetes-certificates-work/&#34; target=&#34;_blank&#34;&gt;How Kubernetes certificate authorities work&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;今天让我们聊聊 Kubernetes 的公私钥和证书认证。&lt;/p&gt;

&lt;p&gt;本文内容会提及如何根据需要对 CA、公私钥进行组织并对集群进行设置。&lt;/p&gt;

&lt;p&gt;Kubernetes 的组件中有很多不同的地方可以放置证书之类的东西。在进行集群安装的时候，我感觉有一百多亿个不同的命令参数是用来设置证书、密钥的，真不明白是怎么弄到一起工作的。&lt;/p&gt;

&lt;p&gt;当然了，没有一百亿那么多的参数，不过的确很多的。比如 API Server 的参数吧，有大概 16 个参数是跟这些东西有关的（下面是节选）：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--cert-dir string                           The directory where the TLS certs are located. If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored. (default &amp;quot;/var/run/kubernetes&amp;quot;)
--client-ca-file string                     If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.
--etcd-certfile string                      SSL certification file used to secure etcd communication.
--etcd-keyfile string                       SSL key file used to secure etcd communication.
--kubelet-certificate-authority string      Path to a cert file for the certificate authority.
--kubelet-client-certificate string         Path to a client cert file for TLS.
--kubelet-client-key string                 Path to a client key file for TLS.
--proxy-client-cert-file string             Client certificate used to prove the identity of the aggregator or kube-apiserver when it must call out during a request. This includes proxying requests to a user api-server and calling out to webhook admission plugins. It is expected that this cert includes a signature from the CA in the --requestheader-client-ca-file flag. That CA is published in the &#39;extension-apiserver-authentication&#39; configmap in the kube-system namespace. Components recieving calls from kube-aggregator should use that CA to perform their half of the mutual TLS verification.
--proxy-client-key-file string              Private key for the client certificate used to prove the identity of the aggregator or kube-apiserver when it must call out during a request. This includes proxying requests to a user api-server and calling out to webhook admission plugins.
--requestheader-allowed-names stringSlice   List of client certificate common names to allow to provide usernames in headers specified by --requestheader-username-headers. If empty, any client certificate validated by the authorities in --requestheader-client-ca-file is allowed.
--requestheader-client-ca-file string       Root certificate bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified by --requestheader-username-headers
--service-account-key-file stringArray      File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify ServiceAccount tokens. If unspecified, --tls-private-key-file is used. The specified file can contain multiple keys, and the flag can be specified multiple times with different files.
--ssh-keyfile string                        If non-empty, use secure SSH proxy to the nodes, using this user keyfile
--tls-ca-file string                        If set, this certificate authority will used for secure access from Admission Controllers. This must be a valid PEM-encoded CA bundle. Alternatively, the certificate authority can be appended to the certificate provided by --tls-cert-file.
--tls-cert-file string                      File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to /var/run/kubernetes.
--tls-private-key-file string               File containing the default x509 private key matching --tls-cert-file.
--tls-sni-cert-key namedCertKey             A pair of x509 certificate and private key file paths, optionally suffixed with a list of domain patterns which are fully qualified domain names, possibly with prefixed wildcard segments. If no domain patterns are provided, the names of the certificate are extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns trump over extracted names. For multiple key/certificate pairs, use the --tls-sni-cert-key multiple times. Examples: &amp;quot;example.crt,example.key&amp;quot; or &amp;quot;foo.crt,foo.key:*.foo.com,foo.com&amp;quot;. (default [])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来是 Controller Manager 的：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--cluster-signing-cert-file string          Filename containing a PEM-encoded X509 CA certificate used to issue cluster-scoped certificates (default &amp;quot;/etc/kubernetes/ca/ca.pem&amp;quot;)
--cluster-signing-key-file string           Filename containing a PEM-encoded RSA or ECDSA private key used to sign cluster-scoped certificates (default &amp;quot;/etc/kubernetes/ca/ca.key&amp;quot;)
--root-ca-file string                       If set, this root certificate authority will be included in service account&#39;s token secret. This must be a valid PEM-encoded CA bundle.
--service-account-private-key-file string   Filename containing a PEM-encoded private RSA or ECDSA key used to sign service account tokens.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再来个 Kubelet：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--client-ca-file string                   If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.
--tls-cert-file string                    File containing x509 Certificate used for serving HTTPS (with intermediate certs, if any, concatenated after server cert). If --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to the directory passed to --cert-dir.
--tls-private-key-file string             File containing x509 private key matching --tls-cert-file.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;本文假设读者：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;对 TLS 认证和 CA 有一些了解。&lt;/li&gt;
&lt;li&gt;能把这些东西跑起来，但是不知道为啥。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面还会说明在 Kubernetes 中的不同 CA，以及不同 CA 的协同工作。&lt;/p&gt;

&lt;p&gt;另外有些我在工作中学到的一些：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;不要用 CA 来检查 Service Account Key。Service Account key 有点古怪，他跟其他的 Key 不是一同处理的。&lt;/li&gt;
&lt;li&gt;如果 kubernetes 建立用户和组的方式不适合需求，可以（应该）设置一个认证代理。&lt;/li&gt;
&lt;li&gt;API Server 如果设置了太多 CA，会显得有点乱。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;这个题目有点复杂，如果阅读中发现任何问题请通知我。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;pki-和-kubernetes&#34;&gt;PKI 和 Kubernetes&lt;/h2&gt;

&lt;p&gt;在阅读 Kubernetes 材料的过程中，我注意到一个词出现了很多次：“PKI”，我不很清楚这是个什么。&lt;/p&gt;

&lt;p&gt;如果你有个在运行的 Kubernetes 集群，其中可能有几百上千个公钥私钥（客户端认证、服务认证等等）。&lt;/p&gt;

&lt;p&gt;如果这几百个 Key 是独立的互不相关的，就会让安全性堕入泥潭。因此我们需要个 CA，CA 的职责就是签发证书，并告诉用户“这个公钥是我发的，靠谱”。&lt;/p&gt;

&lt;p&gt;PKI 就是组织 Key 的方式 —— 什么 Key 是用什么 CA 签发的。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;可以为每个集群准备一个 CA，集群所有的私钥都从这个 CA 签发（Kubernetes 文档中多数是这个情况）。&lt;/li&gt;
&lt;li&gt;可以准备一个全局 CA，所有的私钥都从此而来。&lt;/li&gt;
&lt;li&gt;单独给服务使用一个 CA，对外可见；内部另外使用一个 CA 作为专门用途。&lt;/li&gt;
&lt;li&gt;还有其他。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我不是安全专家，所以不想说如何管理私钥和 CA 才更好。但是不管你用的什么样的模型，其实都可以跟 Kubernetes 协调工作。&lt;/p&gt;

&lt;p&gt;下面根据需求来确认管理 PKI 的方式，以及如何在 Kubernetes 中实现。&lt;/p&gt;

&lt;p&gt;Kubernetes 集群需要一个单根结构的 CA 么？不&lt;/p&gt;

&lt;p&gt;如果你读了不少 Kubernetes 集群的安装文档，会注意到总有一步：设置一个 CA。Kelsey Hightower 的大作 “Kubernetes the hard way” 中是第二步，“在集群中信任 TLS ”中说：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;每个 Kubernetes 集群都有一个集群根 CA。CA 一般用于集群中的组件验证 API Server 的合法性，在 API Serverl 来说，就是验证 kubelet 客户端的证书，诸如此类的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;基本上来说：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;设置一个 CA&lt;/li&gt;
&lt;li&gt;用这个 CA 生成不同的证书，给 Kubernetes 集群的不同组件使用。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果不想为每个集群设置一个新的 CA 呢？可能有很多理由。但是我担心，最终还是需要提供一个根 CA。&lt;/p&gt;

&lt;p&gt;这好像说上面的话是假的，其实可以使用很多不同的 CA 签发的证书来管理 Kubernetes。总的说来，还是要结合具体场景的需求。&lt;/p&gt;

&lt;p&gt;接下来我们来探讨一下证书相关的参数，以及互相之间的关系。每一节都会包含一个可以定义的 CA。每一个都是独立的，并不需要一致。不过在实际操作中，可能你并不想管理 6 个不同的 CA。&lt;/p&gt;

&lt;h2 id=&#34;api-server-的-tls-证书-以及-ca&#34;&gt;API Server 的 TLS 证书（以及 CA）&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--tls-cert-file string&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;包含缺省的 x509 https 认证的文件（可以包含 CA 证书），如果启用了 HTTPS 服务，又没有指定&lt;code&gt;--tls-cert-file&lt;/code&gt;和&lt;code&gt;--tls-private-key-file&lt;/code&gt;参数，就会在 &lt;code&gt;/var/run/kubernetes&lt;/code&gt;生成一个自签名证书以及 Key&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--tls-private-key-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;--tls-cert-file&lt;/code&gt;证书的 x509 私钥。&lt;/p&gt;

&lt;p&gt;如果用 TLS 连接 API Server，就需要这两个参数来选择 API Server 使用的证书。&lt;/p&gt;

&lt;p&gt;证书设置了之后，还需要给各个组件的 kubeconfig 文件进行相关设置。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;current-context: my-context
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /path/to/my/ca.crt # 签发 API Server 证书的 CA 证书
    server: https://horse.org:4443
  name: my-cluster
kind: Config
users:
- name: green-user
  user:
    client-certificate: path/to/my/client/cert # 后面会讲
    client-key: path/to/my/client/key # 后面会讲
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有个让我惊奇的事情就是——这个宇宙里面的几乎所有其他使用 TLS 的系统都会去 &lt;code&gt;/etc/ssl&lt;/code&gt; 查找一个本机信任的 CA 列表，但是 Kubernetes 很傲娇，他不会去找，必须显式的进行告知签发 CA。&lt;/p&gt;

&lt;p&gt;可以使用参数&lt;code&gt;--kubeconfig /path/to/kubeconfig.yaml&lt;/code&gt;将配置文件分配给各个组件进行使用。&lt;/p&gt;

&lt;p&gt;这样我们完成了第一个 CA 的设置：签发 API Server 证书的 CA。这个 CA 跟其他的 CA 可以不一致。&lt;/p&gt;

&lt;h2 id=&#34;api-server-客户证书认证&#34;&gt;API Server 客户证书认证&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--client-ca-file**&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果设置了这一参数，所有的请求都应该使用该文件中所包含的 CA 签发的证书进行签署，证书中的 Common Name 会作为用户名进行使用。&lt;/p&gt;

&lt;p&gt;Kubernetes 组件获得 API Server 认证的方法之一就是使用这一参数。&lt;/p&gt;

&lt;p&gt;所有客户端证书都应该由这一 CA 签发（不需要和 API Server 的 CA 一致）。&lt;/p&gt;

&lt;p&gt;当使用 kubeconfig 文件的时候，可以按照如下方式设置使用证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Config
users:
- name: green-user
  user:
    client-certificate: path/to/my/client/cert
    client-key: path/to/my/client/key
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubernetes 做了很多用户证书方面的假设（用户名就是 Common Name，Group 就是 Organization）。如果这些假设不符合需求，那么就应该&lt;strong&gt;停用客户端证书认证&lt;/strong&gt;，改用认证代理。&lt;/p&gt;

&lt;h2 id=&#34;请求-header-的证书认证-或者-认证代理&#34;&gt;请求 Header 的证书认证（或者：认证代理）&lt;/h2&gt;

&lt;h3 id=&#34;api-server-参数&#34;&gt;API server 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--requestheader-allowed-names stringSlice&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;--requestheader-username-headers&lt;/code&gt; 中指定的 Header 中包含用户名，这一参数的列表确定了允许有效的 Common Name，如果这一参数的列表为空，则所有通过&lt;code&gt;--requestheader-client-ca-file&lt;/code&gt;校验的都允许通过。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--requestheader-client-ca-file string&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;针对收到的请求，在信任&lt;code&gt;--requestheader-username-headers&lt;/code&gt;中指定的 Header 里面包含的用户名之前，首先会用这一 CA 对客户证书进行验证。&lt;/p&gt;

&lt;p&gt;另外一个设置 Kubernetes 认证的方式就是认证代理。如果你对如何向 API Server 发送用户名和组有很多想法，可以设置一个代理，这一代理会使用 HTTP Header 将用户名和组发送给 API Server。&lt;/p&gt;

&lt;p&gt;文档中简单的解释了一下工作方式。代理使用一个客户端证书表明身份，&lt;code&gt;--requestheader-client-ca-file&lt;/code&gt;告知 API Server，该证书所属的 CA。&lt;/p&gt;

&lt;p&gt;我觉得——API Server 有太多认证方式了（客户端认证、认证代理、Token 等等），让人很迷惑。建议用户尽量少的同时使用认证方式，便于管理、使用和除错。&lt;/p&gt;

&lt;h2 id=&#34;service-account-私钥-不是-ca-签发的&#34;&gt;service account 私钥（不是 CA 签发的）&lt;/h2&gt;

&lt;h3 id=&#34;api-server-参数-1&#34;&gt;API Server 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--service-account-key-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PEM 编码的 X509 RSA 或者 ECDSA 的私钥或者公钥，用于检验 ServiceAccount 的 token。如果没指定的话，会使用&lt;code&gt;--tls-private-key-file&lt;/code&gt;替代。文件中可以包含多个 Key，这一参数可以重复指定多个文件。&lt;/p&gt;

&lt;h3 id=&#34;controller-manager-参数&#34;&gt;Controller Manager 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--service-account-private-key-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PEM 编码的 X509 RSA 或者 ECDSA Key，用于签署 Service Account Token。&lt;/p&gt;

&lt;p&gt;Controller Manager 使用私钥签署 Service Account Token。跟 Kubernetes 中使用的其他私钥不同的是，这个私钥是不支持同一 CA 验证的，因此上，需要给每个 Controller Manager 指定一致的私钥文件。&lt;/p&gt;

&lt;p&gt;这个 Key 也不需要什么 CA 来做签署，生成很容易：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;openssl genrsa -out private.key 4096
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后分发给每个 Controller Manager 和 API Server 就可以了。&lt;/p&gt;

&lt;p&gt;使用和 &lt;code&gt;--tls-private-key-file&lt;/code&gt; 一致的文件是可以工作的——只要你给每个 API Server 用的都是同一个 TLS Key（一般都这么做的吧？）。（这里我假设你运行的一个有高可用支持的，多个 API Server 和多个 Controller Manager同时运行的集群）&lt;/p&gt;

&lt;p&gt;如果两个不同的 Controller Manager 用了两个不同的 Key，那就杯具了，他们会用各自的 Key 来生成 Token，最终导致无效判定。我觉得这点不太合理，Kubernetes 应该和其他方面一样，使用 CA 进行管理。通过对源码的月度，我觉得原因可能是 jwt-go 不支持 CA。&lt;/p&gt;

&lt;h2 id=&#34;kubelet-证书认证&#34;&gt;Kubelet 证书认证&lt;/h2&gt;

&lt;p&gt;总算到了 Kubelet 环节了，下面是 API Server 和 Kubelet 相关的内容：&lt;/p&gt;

&lt;h3 id=&#34;api-server-参数-2&#34;&gt;API Server 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--kubelet-certificate-authority&lt;/code&gt;： CA 证书的路径。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;--kubelet-client-certificate&lt;/code&gt;： TLS 证书文件&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;--kubelet-client-key** TLS Key&lt;/code&gt;： 文件&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;kubelet-参数&#34;&gt;Kubelet 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--client-ca-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;请求中的客户端证书如果是由文件中的 CA 签署的，那么他的 Common Name 就会被用作 ID 进行认证。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--tls-cert-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用来提供 HTTPS 服务的 x509 证书（其中也可包含中间人证书）。如果不提供 &lt;code&gt;--tls-cert-file&lt;/code&gt; 和&lt;code&gt;--tls-private-key-file&lt;/code&gt;，就会为主机地址生成一个自签名的证书和对应的 Key，并保存到 &lt;code&gt;--cert-dir&lt;/code&gt; 目录里。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--tls-private-key-file&lt;/code&gt;：&lt;code&gt;--tls-cert-file&lt;/code&gt; 对应的 Key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;校验 kubelet 的请求是有用的，因为 Kubelet 的职责就是在主机上执行代码。&lt;/p&gt;

&lt;p&gt;这里实际上有两个 CA，这里不准备深入描述，情况和 API Server 是一样的，Kubelet 用 TLS 来进行认证，也支持客户证书认证。&lt;/p&gt;

&lt;p&gt;另外还要告知 API Server，用什么 CA 检查 Kubelet 的 TLS，另外用什么证书来跟 Kubelet 通信。&lt;/p&gt;

&lt;p&gt;再说一次，这两个 CA 是可以不同的。&lt;/p&gt;

&lt;h2 id=&#34;太多-ca-了&#34;&gt;太多 CA 了&lt;/h2&gt;

&lt;p&gt;现在我们找到了五个不同的 CA，他们各自独立的为 Kubernetes 提供支持。&lt;/p&gt;

&lt;p&gt;其实还有一些没讨论到的证书，不过希望本文能给你阅读官方文档提供一点帮助。&lt;/p&gt;

&lt;p&gt;当然了，每个 CA 独立设置可能不是必要的，我是希望帮助读者理解这些东西如何设置使之符合各种需求，而不是简单的面向文档照本宣科。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的几种存储</title>
      <link>/post/storage-in-kubernetes/</link>
      <pubDate>Sat, 12 Aug 2017 00:07:43 +0800</pubDate>
      <guid>/post/storage-in-kubernetes/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;参考：&lt;code&gt;https://kubernetes.io/docs/concepts/storage/volumes/&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;一个运行中的容器，缺省情况下，对文件系统的写入，都是发生在其分层文件系统的可写层的，一旦容器运行结束，所有写入都会被丢弃。因此需要对持久化支持。&lt;/p&gt;

&lt;p&gt;Kubernetes 中通过 Volume 的方式提供对存储的支持。下面对一些常见的存储概念进行一点简要的说明。&lt;/p&gt;

&lt;h2 id=&#34;emptydir&#34;&gt;EmptyDir&lt;/h2&gt;

&lt;p&gt;顾名思义，&lt;code&gt;EmptyDir&lt;/code&gt;是一个空目录，他的生命周期和所属的 Pod 是完全一致的，可能读者会奇怪，那还要他做什么？&lt;code&gt;EmptyDir&lt;/code&gt;的用处是，可以在同一 Pod 内的不同容器之间共享工作过程中产生的文件。&lt;/p&gt;

&lt;p&gt;缺省情况下，EmptyDir 是使用主机磁盘进行存储的，也可以设置&lt;code&gt;emptyDir.medium&lt;/code&gt; 字段的值为&lt;code&gt;Memory&lt;/code&gt;，来提高运行速度，但是这种设置，对该卷的占用会消耗容器的内存份额。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: gcr.io/google_containers/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;hostpath&#34;&gt;HostPath&lt;/h2&gt;

&lt;p&gt;这种会把宿主机上的指定卷加载到容器之中，当然，如果 Pod 发生跨主机的重建，其内容就难保证了。&lt;/p&gt;

&lt;p&gt;这种卷一般和&lt;code&gt;DaemonSet&lt;/code&gt;搭配使用，用来操作主机文件，例如进行日志采集的 FLK 中的 FluentD 就采用这种方式，加载主机的容器日志目录，达到收集本主机所有日志的目的。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: gcr.io/google_containers/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;nfs-glusterfs-cephfs-aws-gce-等等&#34;&gt;NFS/GlusterFS/CephFS/AWS/GCE 等等&lt;/h2&gt;

&lt;p&gt;作为一个容器集群，支持网络存储自然是重中之重了，Kubernetes 支持为数众多的云提供商和网络存储方案。&lt;/p&gt;

&lt;p&gt;各种支持的方式不尽相同，例如 GlusterFS 需要创建 Endpoint，Ceph/NFS 之流就没这么麻烦了。&lt;/p&gt;

&lt;p&gt;各种个性配置可移步参考文档。&lt;/p&gt;

&lt;h2 id=&#34;configmap-和-secret&#34;&gt;ConfigMap 和 Secret&lt;/h2&gt;

&lt;p&gt;镜像使用的过程中，经常需要利用配置文件、启动脚本等方式来影响容器的运行方式，如果仅有少量配置，我们可以使用环境变量的方式来进行配置。然而对于一些较为复杂的配置，例如 Apache 之类，就很难用这种方式进行控制了。另外一些敏感信息暴露在 YAML 中也是不合适的。&lt;/p&gt;

&lt;p&gt;ConfigMap 和 Secret 除了使用文件方式进行应用之外，还有其他的应用方式；这里仅就文件方式做一点说明。&lt;/p&gt;

&lt;p&gt;例如下面的 ConfigMap，将一个存储在 ConfigMap 中的配置目录加载到卷中。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: gcr.io/google_containers/busybox
      command: [ &amp;quot;/bin/sh&amp;quot;, &amp;quot;-c&amp;quot;, &amp;quot;ls /etc/config/&amp;quot; ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        # Provide the name of the ConfigMap containing the files you want
        # to add to the container
        name: special-config
  restartPolicy: Never
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注意，这里的 ConfigMap 会映射为一个目录，ConfigMap 的 Key 就是文件名，每个 Value 就是文件内容，比如下面命令用一个目录创建一个 ConfigMap：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl create configmap \
game-config \
--from-file=docs/user-guide/configmap/kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;hr /&gt;

&lt;p&gt;创建一个 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl create secret generic \
db-user-pass --from-file=./username.txt \
--from-file=./password.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 Volume 加载 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: mypod
  namespace: myns
spec:
  containers:
    - name: mypod
      image: redis
      volumeMounts:
        - name: foo
          mountPath: /etc/foo
          readOnly: true
  volumes:
    - name: foo
      secret:
        secretName: mysecret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到 Secret 和 ConfigMap 的创建和使用是很相似的。在 RBAC 中，Secret 和 ConfigMap 可以进行分别赋权，以此限定操作人员的可见、可控权限。&lt;/p&gt;

&lt;h2 id=&#34;pv-pvc&#34;&gt;PV &amp;amp; PVC&lt;/h2&gt;

&lt;p&gt;PersistentVolume 和 PersistentVolumeClaim 提供了对存储支持的抽象，也提供了基础设施和应用之间的分界，管理员创建一系列的 PV 提供存储，然后为应用提供 PVC，应用程序仅需要加载一个 PVC，就可以进行访问。&lt;/p&gt;

&lt;p&gt;而 1.5 之后又提供了 PV 的动态供应。可以不经 PV 步骤直接创建 PVC。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;参考：&lt;code&gt;http://blog.fleeto.us/translation/dynamic-provisioning-and-storage-classes-kubernetes-0&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Istio，Kubernetes 的微服务支持</title>
      <link>/post/istio-for-kubernetes-microservice/</link>
      <pubDate>Sun, 16 Jul 2017 17:17:08 +0800</pubDate>
      <guid>/post/istio-for-kubernetes-microservice/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;参考资料：
&lt;a href=&#34;https://developer.ibm.com/dwblog/2017/istio/&#34; target=&#34;_blank&#34;&gt;https://developer.ibm.com/dwblog/2017/istio/&lt;/a&gt;
&lt;a href=&#34;http://blog.kubernetes.io/2017/05/managing-microservices-with-istio-service-mesh.html&#34; target=&#34;_blank&#34;&gt;http://blog.kubernetes.io/2017/05/managing-microservices-with-istio-service-mesh.html&lt;/a&gt;
&lt;a href=&#34;https://istio.io/docs/&#34; target=&#34;_blank&#34;&gt;https://istio.io/docs/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;Istio 是一个由 IBM、Google 以及 Lyft 联合推出的开源软件，以无痛方式为运行在 Kubernetes 上的微服务提供流量管理，访问策略管理以及监控等功能。这一软件目前仅在 Kubernetes 上运行，今后可能会扩展到其他平台。本文会结合官方例子，完成安装和基础的监控内容。&lt;/p&gt;

&lt;h2 id=&#34;架构和组件&#34;&gt;架构和组件&lt;/h2&gt;

&lt;p&gt;总体架构如图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://istio.io/docs/concepts/what-is-istio/img/architecture/arch.svg&#34; alt=&#34;arch&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;envoy&#34;&gt;Envoy&lt;/h3&gt;

&lt;p&gt;一个 C++ 编写的高性能代理服务器，这里做了扩展，在 Istio 中会以 Sidecar 方式跟应用运行在同一 Pod 内，一方面可以接收并执行关于规则、流量拆分等方面的指令，另一方面能够产生各种指标用于监控和跟踪。&lt;/p&gt;

&lt;h3 id=&#34;mixer&#34;&gt;Mixer&lt;/h3&gt;

&lt;p&gt;Mixer 组件，主要进行访问控制以及策略控制，同时也负责从 Envoy 中获取各项指标。&lt;/p&gt;

&lt;h3 id=&#34;pilot&#34;&gt;Pilot&lt;/h3&gt;

&lt;p&gt;Pilot 是用户和 Isito 之间的桥梁，负责接收各种配置，并发送给各个组件。&lt;/p&gt;

&lt;h3 id=&#34;istio-auth&#34;&gt;Istio auth&lt;/h3&gt;

&lt;p&gt;内置认证和凭证管理，利用 TLS 提供服务之间、用户和服务之间的认证。
可以用来将没有加密支持的服务升级为加密版本，并且在网络策略之外，提供服务级别的策略控制，今后还会增加更多的鉴权和审计方面的能力。&lt;/p&gt;

&lt;h3 id=&#34;功能-特性&#34;&gt;功能 &amp;amp; 特性&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无需对现有服务进行变更&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;支持 http 1.1/2、gRPC 以及 TCP 流量的负载均衡和故障转移&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;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;h3 id=&#34;先决条件&#34;&gt;先决条件&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;li&gt;互联网连接&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;首先去&lt;code&gt;https://github.com/istio/istio/releases&lt;/code&gt;下载最新版本（目前为 0.1.6），解压并设置 istioctl 命令的执行路径 。下面命令中描述的相对目录就是基于此目录&lt;/p&gt;

&lt;p&gt;执行命令&lt;code&gt;kubectl api-versions | grep rbac&lt;/code&gt;查看当前集群的 RBAC 设置：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;如果返回内容为空则无需理会。&lt;/li&gt;
&lt;li&gt;如果返回内容中包含&lt;code&gt;beta&lt;/code&gt;，则需要运行&lt;code&gt;kubectl apply -f install/kubernetes/istio-rbac-beta.yaml
&lt;/code&gt;来进行授权&lt;/li&gt;
&lt;li&gt;如果返回内容中只有&lt;code&gt;alpha&lt;/code&gt; ，就要&lt;code&gt;kubectl apply -f install/kubernetes/istio-rbac-beta.yaml
&lt;/code&gt;创建相应的角色和绑定了。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;运行&#34;&gt;运行&lt;/h3&gt;

&lt;p&gt;最后，运行&lt;code&gt;kubectl create -f install/kubernetes/istio.yaml&lt;/code&gt;创建一系列的 Deployment 以及 Services。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注1： 其实上面的内容照做并不一定能成功，反正我的 1.6.6 上面是没能成功，Pilot Pod 中的 API Server 容器会出错，错误信息大概是“system:serviceaccount:default:istio-pilot-service-account&amp;rdquo; cannot get thirdpartyresources.extensions at the cluster scope.”，也就证明了 beta.yaml 中的 RBAC 设置是不够的，这里我把几个 RoleBinding 都改成为 ClusterRoleBinding，运行就成功了。&lt;/p&gt;

&lt;p&gt;注2：istio.yaml 中的 Ingress 服务是 Loadbalancer 类型的，如果测试集群不具备这样的条件，还请自行修改成其他合适内容。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;至此，基础的 istio 组件就已经运行完毕了。&lt;/p&gt;

&lt;h2 id=&#34;运行应用&#34;&gt;运行应用&lt;/h2&gt;

&lt;p&gt;安装包内包含了一个叫 bookinfo 的小应用，由 Product（入口页）、Detail 和 Review 三部分组成，具体应用 YAML 在安装目录的&lt;code&gt;samples/apps/bookinfo/bookinfo-v1.yaml&lt;/code&gt;文件中。打开文件我们会发现这是个很简单的小应用，无非是几个 Deployment 和 Service 的组合。&lt;/p&gt;

&lt;p&gt;该应用的微服务相互关系大致如下图所示，这个例子很好的展示了 istio 的一大特性——不拘泥于某种语言。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/c26c9a2771b4008770270ba15c6a0f8f.png&#34; alt=&#34;v1&#34; /&gt;&lt;/p&gt;

&lt;p&gt;istio 提供了一个工具叫 istioctl，这个工具的功能之一，就是把普通的应用 YAML 注入为 istio 支持的应用模式，例如：&lt;code&gt;istioctl kube-inject -f bookinfo-v1.yaml &amp;gt; bookinfo-istio-v1.yaml&lt;/code&gt;，比较新旧两个文件不难发现，这一工具为每个 Pod 新增了一个名为 proxy 的容器，以此接管流量，给监控和管理打下基础。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/42138edb04f53163c7a97702cfc66698.png&#34; alt=&#34;v1+istio&#34; /&gt;&lt;/p&gt;

&lt;p&gt;接下来我们运行这一应用：
- &lt;code&gt;kubectl create -f bookinfo-istio-v1.yaml&lt;/code&gt;：运行注入后的 应用
- &lt;code&gt;kubectl create -f bookinfo-ingress.yaml&lt;/code&gt;：创建 Ingress 资源&lt;/p&gt;

&lt;h2 id=&#34;监控&#34;&gt;监控&lt;/h2&gt;

&lt;p&gt;istio 内置了对 ServiceGraph、Prometheus 以及 Zipkin 的支持，简单的运行一下&lt;code&gt;kubectl create -f install/kubernetes/addons&lt;/code&gt;，就会启用这几个服务。注意这几个服务使用的也都是 Loadbalancer 模式，读者应根据集群情况自行修改。&lt;/p&gt;

&lt;p&gt;各个服务启动之后，可以使用负载工具例如 wrk 对 Productpage 页面进行一段时间的访问，来模拟工作情况。&lt;/p&gt;

&lt;h2 id=&#34;grafana&#34;&gt;Grafana&lt;/h2&gt;

&lt;p&gt;Grafana 启动后，我们可以使用服务暴露的地址进行访问，在其中可以看到如下图所示的 Dashboard，展示了我们应用中的几个服务的各种监控数据。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/16a47d78ee8e4046f8e8a90f5ded543f.png&#34; alt=&#34;grafana&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;service-graph&#34;&gt;Service Graph&lt;/h2&gt;

&lt;p&gt;这一个服务提供了两个入口，一个是 &lt;code&gt;/dotvz&lt;/code&gt;，以图形的方式供服务关系展示，而&lt;code&gt;/graph&lt;/code&gt;则提供了 JSON 格式的文档供结构化使用。例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;nodes&amp;quot;: {
        &amp;quot;details&amp;quot;: {},
        &amp;quot;productpage&amp;quot;: {},
        &amp;quot;reviews&amp;quot;: {},
        &amp;quot;unknown&amp;quot;: {}
    },
    &amp;quot;edges&amp;quot;: [
        {
            &amp;quot;source&amp;quot;: &amp;quot;productpage&amp;quot;,
            &amp;quot;target&amp;quot;: &amp;quot;reviews&amp;quot;,
            &amp;quot;labels&amp;quot;: {
                &amp;quot;qps&amp;quot;: &amp;quot;0.030416&amp;quot;,
                &amp;quot;version&amp;quot;: &amp;quot;v1&amp;quot;
            }
        },
        {
            &amp;quot;source&amp;quot;: &amp;quot;unknown&amp;quot;,
            &amp;quot;target&amp;quot;: &amp;quot;productpage&amp;quot;,
            &amp;quot;labels&amp;quot;: {
                &amp;quot;qps&amp;quot;: &amp;quot;0.030416&amp;quot;,
                &amp;quot;version&amp;quot;: &amp;quot;v1&amp;quot;
            }
        },
        {
            &amp;quot;source&amp;quot;: &amp;quot;productpage&amp;quot;,
            &amp;quot;target&amp;quot;: &amp;quot;details&amp;quot;,
            &amp;quot;labels&amp;quot;: {
                &amp;quot;qps&amp;quot;: &amp;quot;0.030416&amp;quot;,
                &amp;quot;version&amp;quot;: &amp;quot;v1&amp;quot;
            }
        }
    ]
}	
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;zipkin&#34;&gt;Zipkin&lt;/h2&gt;

&lt;p&gt;Istio 提供了开箱即用的 Zipkin 跟踪支持，打开页面之后，我们会看到 Zipkin 的查找页面，并无特殊内容，就不展开细谈了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/89331f5f59d1ba743b2d839763198946.png&#34; alt=&#34;zipkin&#34; /&gt;&lt;/p&gt;

&lt;p&gt;文中介绍内容只是很粗浅的入门操作，后续会进一步跟进、挖掘和学习这一系统的其他强大功能。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 Kubernetes 中使用 Fluent Bit 进行日志采集</title>
      <link>/post/fluentbit-in-k8s/</link>
      <pubDate>Mon, 10 Jul 2017 08:51:52 +0800</pubDate>
      <guid>/post/fluentbit-in-k8s/</guid>
      <description>&lt;p&gt;Fluent Bit 和 Fluentd 一样，是 Treasure Data 资助的采集工具，二者对比如下：&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Fluentd&lt;/th&gt;
&lt;th&gt;FluentBit&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;嵌入设备和 IoT 设备&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;内存&lt;/td&gt;
&lt;td&gt;约 20 MB&lt;/td&gt;
&lt;td&gt;约 150 KB&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;语言&lt;/td&gt;
&lt;td&gt;C 和 Ruby&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;性能&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;依赖&lt;/td&gt;
&lt;td&gt;以 Ruby Gem 构建，依赖一系列的 Gem&lt;/td&gt;
&lt;td&gt;零依赖，可能有些插件会有依赖。&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;插件&lt;/td&gt;
&lt;td&gt;超过三百个&lt;/td&gt;
&lt;td&gt;目前15个左右&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;授权&lt;/td&gt;
&lt;td&gt;Apache License v2.0&lt;/td&gt;
&lt;td&gt;Apache License v2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;从上表可以看出，Fluentd 具有众多插件，随之而来的是很好的弹性。而 Fluent Bit 则更适用于嵌入设备等资源受限的场景。另外二者并非互斥关系，Fluent Bit 提供了输出插件，可以把数据发给 Fluentd，因此他们可以在系统中作为独立服务互相协作。&lt;/p&gt;

&lt;p&gt;Fluent Bit 也提供了 Kubernetes Filter 插件，用于将采集到的日志结合对 Kubernetes API 的查询，为日志加入 Kubernetes 的相关数据，例如 Pod 信息、容器信息、命名空间以及标签和注解等内容。&lt;/p&gt;

&lt;p&gt;仅就此来说，Fluent Bit 是可以替代 Kubernetes 缺省推荐的 Fluentd 进行日志采集工作的，经过笔者测试，可以直接使用他替代原有的 Fluentd，使用 DaemonSet 运行，结合 Elastic Search 进行日志归集工作。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;详情参见：&lt;a href=&#34;http://fluentbit.io/documentation/0.11/filter/kubernetes.html&#34; target=&#34;_blank&#34;&gt;http://fluentbit.io/documentation/0.11/filter/kubernetes.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;简单的使用如下命令就可以运行：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-daemonset/master/fluent-bit-daemonset-elasticsearch.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这一 YAML 文件中的镜像版本为 0.11，具体版本更新可以到 Docker Hub 进行查询，其中包含的缺省 elasticsearch 地址为 elasticsearch-logging，端口为 9200，如上配置如果不符，可以下载文件自行修改运行。&lt;/p&gt;

&lt;p&gt;另外目前 RBAC 的访问控制模式已经成为缺省，在启用了 RBAC 模式的集群中，该 Pod 的运行是无法成功的，具体表现是日志中出现无法获取 Pod 元数据的信息，这是因为缺省情况下，这一 YAML 中使用的是 &lt;code&gt;kube-system&lt;/code&gt; 中的 &lt;code&gt;default&lt;/code&gt;  Service Account，这一服务账号并不具备获取 Pod 信息的授权，要成功运行，就必须按照 RBAC 的规矩，让 Fluent Bit 的 Service Account 能够获取 Pod 信息，可以用如下方式来解决：&lt;/p&gt;

&lt;p&gt;首先为 Fluent bit 创建专门的 Service Account：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    kubectl create sa logging -n kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后为日志收集器创建角色，让该角色可以读取 Pod 信息。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: pod-reader
rules:
- apiGroups: [&amp;quot;&amp;quot;]
  resources: [&amp;quot;pods&amp;quot;]
  verbs: [&amp;quot;get&amp;quot;, &amp;quot;watch&amp;quot;, &amp;quot;list&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来创建 ClusterRoleBinding，把新建的角色和 Service Account 绑定在一起：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: read-pods-global
subjects:
- kind: ServiceAccount
  name: logging
  namespace: kube-system
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后在 Fluent Bit 的 yaml 中加入 Service Account 的指派：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# 省略若干
spec:
  template:
    metadata:
      labels:
        k8s-app: fluent-bit-logging
        version: v1
        kubernetes.io/cluster-service: &amp;quot;true&amp;quot;
    spec:
      serviceAccountName: logging
# 省略若干
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;经过这一番折腾之后，Fluent Bit 就可以在开启 RBAC 的 1.&lt;sup&gt;6&lt;/sup&gt;&amp;frasl;&lt;sub&gt;1&lt;/sub&gt;.7 集群上运行了。打开相应的 Kibana 页面，会看到和标配 Fluentd 一致的日志搜集结果。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;好吧，今天讲的其实是 RBAC。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Kubeadm offline installer 升级到 1.7.0 版本</title>
      <link>/post/kubeadm-offline-installer-1.7.0/</link>
      <pubDate>Thu, 06 Jul 2017 23:19:51 +0800</pubDate>
      <guid>/post/kubeadm-offline-installer-1.7.0/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/fleeto/kubeadm-offline-installer/releases&#34; target=&#34;_blank&#34;&gt;仓库地址&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本来做这玩意的初衷就是，Kubeadm 和 Kubernetes 是一家人，升级比较方便跟得住。未曾想第一次大版本升级，就遇到了个不大不小的坑，导致安装无法完成。这个 Issue 会在 1.7.1 修补，下面介绍一下曲线救国的安装方式。&lt;/p&gt;

&lt;p&gt;这一问题的似乎是 kubeadm 的更新破坏了 TLS 自动授权过程造成的，具体症状是：主节点的 &lt;code&gt;kubeadm init&lt;/code&gt; 完成之后，在其他节点上使用 &lt;code&gt;kubeadm join --token=xxxx host_ip:host_port&lt;/code&gt; 命令加入集群时，集群会反复输出错误信息，大意是 &lt;code&gt;kube-public&lt;/code&gt; 命名空间中名为 &lt;code&gt;cluster-info&lt;/code&gt; 的 ConfigMap 中没有对应 token 的签署记录。&lt;/p&gt;

&lt;p&gt;使用 kubectl 查看该 ConfigMap，和 1.6.6 的集群作对照（是的，安装的够快，想要什么版本都容易），发现 1.7.0 里面这个 ConfigMap 的元素列表确缺少这一块内容。&lt;/p&gt;

&lt;p&gt;既然如此，看样子手工签署也是要不少工夫，干脆转头使用上一版本（1.6.6）的 kubeadm，使用指定版本的方式来安装 1.7.0 的集群。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubeadm init&lt;/code&gt; 过程会顺利完成，然而在 &lt;code&gt;kubeadm join&lt;/code&gt; 过程中，会卡在 &lt;code&gt;CSR&lt;/code&gt; 阶段，经过翻查文档，1.7.0 的自动授权方式有变化，废弃了原有的根据 Group 自动通过的开关，一不做二不休，在 Playbook 的 Master Role 中新建一个 Cron Job，每分钟执行一次如下命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
TOKEN=`kubectl get csr | grep csr | grep -i pending | cut -f1 -d &amp;quot; &amp;quot; | head -n 1`
if [[ -n &amp;quot;$TOKEN&amp;quot; ]]; then
  kubectl certificate approve $TOKEN
  echo &amp;quot;CSR: $TOKEN had been approved.&amp;quot; &amp;gt;&amp;gt; /var/log/auto_approval.log
else
  echo &amp;quot;CSR not found.&amp;quot; &amp;gt;&amp;gt; /var/log/auto_approval.log
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;简单说就是获取在途的 CSR，进行 Approve。
这样在后面的节点进行加入的时候，主节点会每分钟进行一次 Approve，在所有节点成功加入之后，调用 Cleanup 角色，禁用这一功能即可。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;最后，Github 不建议在版本库中存放二进制文件，这里我将所有二进制文件集中到了根目录的 &lt;code&gt;files&lt;/code&gt; 目录下，要顺利使用这一脚本，一定要在 Release 页面中下载相应的压缩包来使用。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>kubeadm 踩坑记</title>
      <link>/post/kubeadm-traps/</link>
      <pubDate>Tue, 27 Jun 2017 08:14:05 +0800</pubDate>
      <guid>/post/kubeadm-traps/</guid>
      <description>&lt;p&gt;Kubeadm 是个让我爱恨交加的东西，一方面，我不认为一个生产集群应该使用这样一个第三方工具进行在线安装，尤其是在目前这种网络环境之下；而另外一方面，Kubeadm 这一工具是随 Kubernetes 同步更新的，其中包含了大量的集群配置方面的最佳实践，是追新的最佳参考，所以这个讨厌的东西的运行是必须需要得到保障的。kubeadm 的执行过程沉默到令人发指，因此下面分享几个使用过程中遇到的一些问题和解决的思路和方法，希望对同行们有所帮助。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;下面的例子是基于 kubeadm 1.6.6 + Centos 7 的执行过程记录的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;写入 yum repo 并进行安装之后，利用 &lt;code&gt;systemctl enable kubelet&lt;/code&gt; 启用 kubelet 服务之后，只要运行一下 &lt;code&gt;systemctl daemon-reload&lt;/code&gt;即可，这一服务的启动需要 kubeadm 生成的证书和配置文件等的支持，因此无需进行启动。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubeadm init&lt;/code&gt;过程首先会检查代理服务器，确定跟 kube-apiserver 的 https 连接方式，如果有代理设置，会提出警告。&lt;/li&gt;
&lt;li&gt;接下来会对 sysctl 进行检查，我这里需要执行 &lt;code&gt;sysctl net.bridge.bridge-nf-call-iptables=1&lt;/code&gt; ，对这一参数进行调整，解决他的警告。&lt;/li&gt;
&lt;li&gt;接下来进入最抓狂的一个等待时间，屏幕显示为&lt;code&gt;[apiclient] Created API client, waiting for the control plane to become ready&lt;/code&gt;，这一过程中会遇到大多数的坑，我一般会另外启动一个连接或者 tmux 窗口，进行观察和除错：

&lt;ul&gt;
&lt;li&gt;这里已经做好运行 kubelet 服务的准备，因此这一时间内，我们可以利用&lt;code&gt;systemctl statusl -l kubelet&lt;/code&gt;对服务的启动状况进行检查，目前比较容易遇到的是 kubectl 和 docker 两个服务的&lt;code&gt;cgroup-driver&lt;/code&gt;不一致的问题，这里编辑文件&lt;code&gt;/etc/systemd/system/kubelet.service.d/10-kubeadm.conf&lt;/code&gt;，修改这一参数值为跟 docker 一致的&lt;code&gt;cgroupfs&lt;/code&gt;即可。&lt;strong&gt;这一步可以在 kubeadm init 之前执行完成&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;kubelet 启动之后，会尝试运行系统组件的 Pod，这里我们可以通过观察&lt;code&gt;docker images&lt;/code&gt;的镜像列表来观察是否能够顺利进行下载。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;镜像下载完成之后就会开始运行各个系统组件，因此也是事故最为集中的阶段&lt;/strong&gt;，我们可以使用&lt;code&gt;docker ps&lt;/code&gt;、&lt;code&gt;docker logs&lt;/code&gt;、&lt;code&gt;docker inspect&lt;/code&gt;几个命令，逐个查看组件的运行情况，对失败组件的原因进行排除，之前提过的&lt;code&gt;resolv.conf&lt;/code&gt;的故障就是在这一阶段发现并排除的。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>实测：Kubernetes 1.6 中的混合 DNS</title>
      <link>/post/kubernetes-dns-ex/</link>
      <pubDate>Sat, 03 Jun 2017 08:39:03 +0800</pubDate>
      <guid>/post/kubernetes-dns-ex/</guid>
      <description>

&lt;p&gt;在之前的文章中提到过，Kubernetes 1.6 新增的混合 DNS 功能。这一功能不大，但是在企业私有云环境下有着非常重要的衔接作用，能够有效的将 Kubernetes 内的应用和集群外甚至互联网上的的 Consual 或者类似系统管理的服务连接起来，形成更好的协同效果。&lt;/p&gt;

&lt;p&gt;上篇文章主要侧重点是概念和一些 YAML 例子，本文则会从操作出发，用一个例子从头到尾的逐步操作这一功能的具体操作。&lt;/p&gt;

&lt;h2 id=&#34;dns-server&#34;&gt;DNS Server&lt;/h2&gt;

&lt;p&gt;我们使用一个 Ubuntu Server 运行 dnsmasq 来担任 Kubernetes 集群外的 DNS 服务器角色，只为了满足测试需要的话，就不需要太复杂的配置了。&lt;/p&gt;

&lt;h3 id=&#34;安装&#34;&gt;安装&lt;/h3&gt;

&lt;p&gt;具体安装步骤如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;#!/bin/sh
# 安装 dnsmasq
apt install dnsmasq
# 停止自动启动的 dnsmasq 服务
systemctl stop dnsmasq
# 生成一个 Host 文件并写入一条记录
echo &amp;quot; 114.114.114.114 server.out-of.kubernetes&amp;quot; &amp;gt; /tmp/hosts
# 显示 IP
ifconfig
# 启动 DNS 服务
# -d 用 debug 模式启动，在前台运行
# -q 输出查询记录
# -h 不使用 /etc/hosts
# -R 不使用 /etc/resolve.conf
# -H 使用刚才生成的文件作为 dns 记录
dnsmasq -q -d -h -q -R -H /tmp/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样，我们就启动了一个 DNS 服务器，并且可以直观的看到其工作状况如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;dnsmasq: started, version 2.76 cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC lo
op-detect inotify
dnsmasq: warning: no upstream servers configured
dnsmasq: bad address at /tmp/hosts line 1
dnsmasq: read /tmp/hosts - 0 addresses
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;kubernetes-配置&#34;&gt;Kubernetes 配置&lt;/h2&gt;

&lt;p&gt;前文说过，我们需要用一个 Configmap 对象来让 Kubernetes 应用新的 DNS 解析配置，这里我们简单的新建一个存根域，就是上文提到的 &lt;code&gt;out-of.kubernetes&lt;/code&gt; 域名，让其使用我们前面安装的 DNS 服务器进行解析，其他域名则使用 &lt;code&gt;8.8.8.8&lt;/code&gt; 和 &lt;code&gt;8.8.4.4&lt;/code&gt; 这一公共 DNS 解析。配置情况如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-dns
  namespace: kube-system
data:
  stubDomains: |
    {&amp;quot;out-of.kubernetes&amp;quot;: [&amp;quot;10.140.0.5&amp;quot;]}
  upstreamNameservers: |
    [&amp;quot;8.8.8.8&amp;quot;, &amp;quot;8.8.4.4&amp;quot;] 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;利用 &lt;code&gt;kubectl apply -f&lt;/code&gt; 命令使之生效。&lt;/p&gt;

&lt;p&gt;注意：系统可能缺省带有这一 configmap，因此需要用 apply 进行操作，建议使用 &lt;code&gt;kubectl get configmap kube-dns -o yaml --namespace kube-system&lt;/code&gt; 进行复查。&lt;/p&gt;

&lt;h2 id=&#34;测试-pod&#34;&gt;测试 Pod&lt;/h2&gt;

&lt;p&gt;这里使用一个 Alpine Linux 的镜像作为测试工具：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: tester
spec:
  containers:
  - name: alpine
    image: alpine
    imagePullPolicy: IfNotPresent
    command: [&amp;quot;sleep&amp;quot;]
    args: [&amp;quot;3600&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;测试&#34;&gt;测试&lt;/h2&gt;

&lt;p&gt;测试 Pod 运行之后，可以使用&lt;code&gt;kubectl exec -it tester sh&lt;/code&gt;命令进入 Pod开始测试。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 Pod 的 Shell 中我们可以尝试执行 &lt;code&gt;ping server.out-of.kubernetes&lt;/code&gt;，会看到返回了我们写入在 Hosts 中的地址，在 DNS 服务器端，也会看到相关的查询记录。&lt;/li&gt;
&lt;li&gt;如果 ping 其他的主机名，则会返回正常的结果。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>针对资源紧缺处理方式的配置</title>
      <link>/post/kubernetes-resource-evict/</link>
      <pubDate>Thu, 01 Jun 2017 19:25:48 +0800</pubDate>
      <guid>/post/kubernetes-resource-evict/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/&#34; target=&#34;_blank&#34;&gt;Configure Out Of Resource Handling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如何在资源紧缺的情况下，保证 Node 的稳定性，是 Kubelet 需要面对的一个重要的问题。尤其对于内存和磁盘这种不可压缩的资源，紧缺就相当于不稳定。&lt;/p&gt;

&lt;h2 id=&#34;驱逐策略&#34;&gt;驱逐策略&lt;/h2&gt;

&lt;p&gt;Kubelet 能够监控资源消耗，来防止计算资源被耗尽。一旦出现资源紧缺的迹象，Kubelet 就会主动终止一或多个 Pod 的运行，以回收紧俏资源。当一个 Pod 被终止时，其中的容器会全部停止，Pod 状态会被置为  &lt;code&gt;Failed&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&#34;驱逐信号&#34;&gt;驱逐信号&lt;/h3&gt;

&lt;p&gt;下文中提到了一些信号，kubelet 能够利用这些信号作为决策依据来触发驱逐行为。描述列中的内容来自于 Kubelet summary API。&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;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;memory.available&lt;/td&gt;
&lt;td&gt;memory.available := node.status.capacity[memory] - node.stats.memory.workingSet&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;nodefs.available&lt;/td&gt;
&lt;td&gt;nodefs.available := node.stats.fs.available&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;nodefs.inodesFree&lt;/td&gt;
&lt;td&gt;nodefs.inodesFree := node.stats.fs.inodesFree&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;imagefs.available&lt;/td&gt;
&lt;td&gt;imagefs.available := node.stats.runtime.imagefs.available&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;imagefs.inodesFree&lt;/td&gt;
&lt;td&gt;imagefs.inodesFree := node.stats.runtime.imagefs.inodesFree&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;上面的每个信号都支持整数值或者百分比。百分比的分母部分就是各个信号的总量。kubelet 支持两种文件系统分区。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;nodefs&lt;/code&gt;：保存 kubelet 的卷和守护进程日志等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;imagefs&lt;/code&gt;：在容器运行时，用于保存镜像以及可写入层。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;imagefs 是可选的。Kubelet 能够利用 cAdvisor 自动发现这些文件系统。Kubelet 不关注其他的文件系统。所有其他类型的配置，例如保存在独立文件系统的卷和日志，都不被支持。&lt;/p&gt;

&lt;p&gt;因为磁盘压力已经被驱逐策略接管，因此未来将会停止对现有 &lt;a href=&#34;http://kubernetes.io/docs/admin/garbage-collection/&#34; target=&#34;_blank&#34;&gt;垃圾收集&lt;/a&gt; 方式的支持。&lt;/p&gt;

&lt;h3 id=&#34;驱逐阈-yù-音同-预-值&#34;&gt;驱逐阈（yù，音同“预”）值：&lt;/h3&gt;

&lt;p&gt;一旦超出阈值，就会触发 kubelet 进行资源回收的动作。阈值的定义方式如下：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;eviction-signal&amp;gt;&amp;lt;operator&amp;gt;&amp;lt;quantity&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;上面的表格中列出了可用的 &lt;code&gt;eviction-signal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;仅有一个 &lt;code&gt;operator&lt;/code&gt; 可用：&lt;strong&gt;&amp;lt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;quantity&lt;/code&gt; 需要符合 Kubernetes 中的描述方式。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如如果一个 Node 有 10Gi 内存，我们希望在可用内存不足 1Gi 时进行驱逐，就可以选取下面的一种方式来定义驱逐阈值：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;memory.available&amp;lt;10%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;memory.available&amp;lt;1Gi&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;驱逐软阈值&#34;&gt;驱逐软阈值&lt;/h4&gt;

&lt;p&gt;软阈值需要和一个宽限期参数协同工作。当系统资源消耗达到软阈值时，这一状况的持续时间超过了宽限期之前，Kubelet 不会触发任何动作。如果没有定义宽限期，Kubelet 会拒绝启动。&lt;/p&gt;

&lt;p&gt;另外还可以定义一个 Pod 结束的宽限期。如果定义了这一宽限期，那么 Kubelet 会使用 &lt;code&gt;pod.Spec.TerminationGracePeriodSeconds&lt;/code&gt; 和最大宽限期这两个值之间较小的那个（进行宽限），如果没有指定的话，kubelet 会不留宽限立即杀死 Pod。&lt;/p&gt;

&lt;p&gt;软阈值的定义包括以下几个参数：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;eviction-soft&lt;/code&gt;：描述一套驱逐阈值（例如 &lt;code&gt;memory.available&amp;lt;1.5Gi&lt;/code&gt; ），如果满足这一条件的持续时间超过宽限期，就会触发对 Pod 的驱逐动作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eviction-soft-grace-period&lt;/code&gt;：包含一套驱逐宽限期（例如 &lt;code&gt;memory.available=1m30s&lt;/code&gt;），用于定义达到软阈值之后，持续时间超过多久才进行驱逐。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eviction-max-pod-grace-period&lt;/code&gt;：在因为达到软阈值之后，到驱逐一个 Pod 之前的最大宽限时间（单位是秒），&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;驱逐硬阈值&#34;&gt;驱逐硬阈值&lt;/h4&gt;

&lt;p&gt;硬阈值没有宽限期，如果达到了硬阈值，kubelet 会立即杀掉 Pod 并进行资源回收。&lt;/p&gt;

&lt;p&gt;硬阈值的定义：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;eviction-hard&lt;/code&gt;：描述一系列的驱逐阈值（比如说 &lt;code&gt;memory.available&amp;lt;1Gi&lt;/code&gt;），一旦达到这一阈值，就会触发对 Pod 的驱逐，缺省的硬阈值定义是：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&amp;ndash;eviction-hard=memory.available&amp;lt;100Mi&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&#34;驱逐监控频率&#34;&gt;驱逐监控频率&lt;/h3&gt;

&lt;p&gt;Housekeeping interval 参数定义一个时间间隔，Kubelet 每隔这一段就会对驱逐阈值进行评估。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;housekeeping-interval&lt;/code&gt;：容器检查的时间间隔。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;节点状况&#34;&gt;节点状况&lt;/h2&gt;

&lt;p&gt;Kubelet 会把驱逐信号跟节点状况对应起来。&lt;/p&gt;

&lt;p&gt;如果触发了硬阈值，或者符合软阈值的时间持续了与其对应的宽限期，Kubelet 就会认为当前节点压力太大，下面的节点状态定义描述了这种对应关系。&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;MemoryPressure&lt;/td&gt;
&lt;td&gt;memory.available&lt;/td&gt;
&lt;td&gt;节点的可用内存达到了驱逐阈值&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;DiskPressure&lt;/td&gt;
&lt;td&gt;nodefs.available, nodefs.inodesFree, imagefs.available, imagefs.inodesFree&lt;/td&gt;
&lt;td&gt;节点的 root 文件系统或者镜像文件系统的可用空间达到了驱逐阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Kubelet 会持续报告节点状态的更新过程，这一频率由参数 &lt;strong&gt;&amp;ndash;node-status-update-frequency&lt;/strong&gt; 指定，缺省情况下取值为 &lt;strong&gt;10s&lt;/strong&gt;。&lt;/p&gt;

&lt;h3 id=&#34;节点状况的波动&#34;&gt;节点状况的波动&lt;/h3&gt;

&lt;p&gt;如果一个节点的状况在软阈值的上下波动，但是又不会超过他的宽限期，将会导致该节点的状态持续的在是否之间徘徊，最终会影响降低调度的决策过程。&lt;/p&gt;

&lt;p&gt;要防止这种状况，下面的标志可以用来通知 Kubelet，在脱离压力状态之前，必须等待。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eviction-pressure-transition-period&lt;/code&gt; 定义了在跳出压力状态之前要等待的时间。&lt;/p&gt;

&lt;p&gt;Kubelet 在把压力状态设置为 False 之前，会确认在周期之内，该节点没有达到逐出阈值。&lt;/p&gt;

&lt;h2 id=&#34;回收节点级别的资源&#34;&gt;回收节点级别的资源&lt;/h2&gt;

&lt;p&gt;如果达到了驱逐阈值，并且超出了宽限期，那么 Kubelet 会开始回收超出限量的资源，直到驱逐信号量回到阈值以内。&lt;/p&gt;

&lt;p&gt;Kubelet 在驱逐用户 Pod 之前，会尝试回收节点级别的资源。如果服务器为容器定义了独立的 imagefs，他的回收过程会有所不同。&lt;/p&gt;

&lt;h3 id=&#34;有-imagefs&#34;&gt;有 Imagefs&lt;/h3&gt;

&lt;p&gt;如果 nodefs 文件系统到达了驱逐阈值，kubelet 会按照下面的顺序来清理空间。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;删除死掉的 Pod/容器&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果 imagefs 文件系统到达了驱逐阈值，kubelet 会按照下面的顺序来清理空间。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;删掉所有无用镜像&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;没有-imagefs&#34;&gt;没有 Imagefs&lt;/h3&gt;

&lt;p&gt;如果 nodefs 文件系统到达了驱逐阈值，kubelet 会按照下面的顺序来清理空间。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;删除死掉的 Pod/容器&lt;/li&gt;
&lt;li&gt;删掉所有无用镜像&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;驱逐用户-pod&#34;&gt;驱逐用户 Pod&lt;/h2&gt;

&lt;p&gt;如果 Kubelet 无法获取到足够的资源，就会开始驱逐 Pod。&lt;/p&gt;

&lt;p&gt;Kubelet 会按照下面的标准对 Pod 的驱逐行为进行评判：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;根据服务质量&lt;/li&gt;
&lt;li&gt;根据 Pod 调度请求的被耗尽资源的消耗量&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接下来，Pod 按照下面的顺序进行驱逐：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BestEffort&lt;/code&gt;：消耗最多紧缺资源的 Pod 最先失败。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Burstable&lt;/code&gt;：相对请求（request）最多紧缺资源的 Pod 最先被驱逐，如果没有 Pod 超出他们的请求，策略会瞄准紧缺资源消耗量最大的 Pod。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Guaranteed&lt;/code&gt;：相对请求（request）最多紧缺资源的 Pod 最先被驱逐，如果没有 Pod 超出他们的请求，策略会瞄准紧缺资源消耗量最大的 Pod。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Guaranteed Pod&lt;/code&gt; 绝不会因为其他 Pod 的资源消费被驱逐。如果系统进程（例如 kubelet、docker、journald 等）消耗了超出 &lt;code&gt;system-reserved&lt;/code&gt; 或者 &lt;code&gt;kube-reserved&lt;/code&gt; 的资源，而且这一节点上只运行了 Guaranteed Pod，那么为了保证节点的稳定性并降低异常消费对其他 Guaranteed Pod 的影响，必须选择一个 Guaranteed Pod 进行驱逐。&lt;/p&gt;

&lt;p&gt;本地磁盘是一个 &lt;code&gt;BestEffort&lt;/code&gt; 资源。如有必要，kubelet 会在 &lt;code&gt;DiskPressure&lt;/code&gt; 的情况下，kubelet 会按照 QoS 进行评估。如果 Kubelet 判定缺乏 inode 资源，就会通过驱逐最低 QoS 的 Pod 的方式来回收 inodes。如果 kubelet 判定缺乏磁盘空间，就会通过在相同 QoS 的 Pods 中，选择消耗最多磁盘空间的 Pod 进行驱逐。&lt;/p&gt;

&lt;h3 id=&#34;有-imagefs-1&#34;&gt;有 Imagefs&lt;/h3&gt;

&lt;p&gt;如果 nodefs 触发了驱逐，Kubelet 会用 nodefs 的使用对 Pod 进行排序 - Pod 中所有容器的本地卷和日志。&lt;/p&gt;

&lt;p&gt;如果 imagefs 触发了驱逐，Kubelet 会根据 Pod 中所有容器的消耗的可写入层进行排序。&lt;/p&gt;

&lt;h3 id=&#34;没有-imagefs-1&#34;&gt;没有 Imagefs&lt;/h3&gt;

&lt;p&gt;如果 nodefs 触发了驱逐，Kubelet 会对各个 Pod 的所有容器的总体磁盘消耗进行排序 —— 本地卷 + 日志 + 写入层。&lt;/p&gt;

&lt;p&gt;在某些场景下，驱逐 Pod 可能只回收了很少的资源。这就导致了 kubelet 反复触发驱逐阈值。另外回收资源例如磁盘资源，是需要消耗时间的。&lt;/p&gt;

&lt;p&gt;要缓和这种状况，Kubelet 能够对每种资源定义 &lt;code&gt;minimum-reclaim&lt;/code&gt;。kubelet 一旦发现了资源压力，就会试着回收至少 &lt;code&gt;minimum-reclaim&lt;/code&gt; 的资源，使得资源消耗量回到期望范围。&lt;/p&gt;

&lt;p&gt;例如下面的配置：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--eviction-hard=memory.available&amp;lt;500Mi,nodefs.available&amp;lt;1Gi,imagefs.available&amp;lt;100Gi
--eviction-minimum-reclaim=&amp;quot;memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi&amp;quot;`
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;memory.available&lt;/code&gt; 被触发，Kubelet 会启动回收，让 &lt;code&gt;memory.available&lt;/code&gt; 至少有 &lt;strong&gt;500Mi&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;如果是 &lt;code&gt;nodefs.available&lt;/code&gt;，Kubelet 就要想法子让 &lt;code&gt;nodefs.available&lt;/code&gt; 回到至少 &lt;strong&gt;1.5Gi&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;而对于 &lt;code&gt;imagefs.available&lt;/code&gt;， kubelet 就要回收到最少 &lt;strong&gt;102Gi&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;缺省情况下，所有资源的 &lt;code&gt;eviction-minimum-reclaim&lt;/code&gt; 为 &lt;strong&gt;0&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&#34;调度器&#34;&gt;调度器&lt;/h2&gt;

&lt;p&gt;在节点资源紧缺的情况下，节点会报告这一状况。调度器以此为信号，不再继续向此节点部署新的 Pod。&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;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MemoryPressure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不再分配新的 &lt;code&gt;BestEffort Pod&lt;/code&gt; 到这个节点&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DiskPressure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不再向这一节点分配 Pod&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&#34;节点的-oom-行为&#34;&gt;节点的 OOM 行为&lt;/h2&gt;

&lt;p&gt;如果节点在 Kubelet 能够回收内存之前，遭遇到了系统的 OOM (内存不足)，节点就依赖 &lt;a href=&#34;https://lwn.net/Articles/391222/&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;oom_killer&lt;/code&gt;&lt;/a&gt; 进行响应了。&lt;/p&gt;

&lt;p&gt;kubelet 根据 Pod 的 QoS 为每个容器设置了一个 &lt;code&gt;oom_score_adj&lt;/code&gt; 值。&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;QoS&lt;/th&gt;
&lt;th&gt;&lt;code&gt;oom_score_adj&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Guaranteed&lt;/td&gt;
&lt;td&gt;-998&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;BestEffort&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Burstable&lt;/td&gt;
&lt;td&gt;&lt;code&gt;min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;如果 kubelet 无法在系统 OOM 之前回收足够的内存，&lt;code&gt;oom_killer&lt;/code&gt; 就会根据根据内存使用比率来计算 &lt;code&gt;oom_score&lt;/code&gt;，得出结果和 &lt;code&gt;oom_score_adj&lt;/code&gt; 相加，最后得分最高的 Pod 会被首先驱逐。&lt;/p&gt;

&lt;p&gt;这一行为的思路是，QoS 最低，相对于调度的 Reqeust 来说又消耗最多内存的 Pod 会被首先清除，来保障内存的回收。&lt;/p&gt;

&lt;p&gt;跟 Pod 驱逐不同，如果一个 Pod 的容器被 OOM 杀掉，他是可能被 kubelet 根据 &lt;code&gt;RestartPolicy&lt;/code&gt; 重启的。&lt;/p&gt;

&lt;h2 id=&#34;最佳时间&#34;&gt;最佳时间&lt;/h2&gt;

&lt;h3 id=&#34;可调度的资源和驱逐策略&#34;&gt;可调度的资源和驱逐策略&lt;/h3&gt;

&lt;p&gt;我们想象如下的场景：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;节点内存容量：10Gi&lt;/li&gt;
&lt;li&gt;保留 10% 的内存容量给系统服务（内核，kubelet 等）。&lt;/li&gt;
&lt;li&gt;在 95% 内存使用率的时候驱逐 Pod，来降低系统 OOM 的发生率。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以我们用这样的参数启动 Kubelet：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--eviction-hard=memory.available&amp;lt;500Mi
--system-reserved=memory=1.5Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个配置中隐含了一个设定就是，系统保留涵盖了驱逐标准。&lt;/p&gt;

&lt;p&gt;要达到这一容量，可能是有的 Pod 使用了超出其请求的数量，或者系统占用了超过 &lt;strong&gt;500Mi&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这样的配置保证了调度器不会向即将发生内存压力的节点分配 Pod，避免触发驱逐。&lt;/p&gt;

&lt;h2 id=&#34;daemonset&#34;&gt;DaemonSet&lt;/h2&gt;

&lt;p&gt;因为 DaemonSet 中的 Pod 会立即重建到同一个节点，所以 Kubelet 不应驱逐 DaemonSet 中的 Pod。&lt;/p&gt;

&lt;p&gt;但是目前 Kubelet 无法分辨一个 Pod 是否由 DaemonSet 创建。如果/当 Kubelet 能够识别这一点，那么就可以先从驱逐候选列表中过滤掉 DaemonSet 的 Pod。&lt;/p&gt;

&lt;p&gt;一般来说，强烈建议 DaemonSet 不要创建 BestEffort Pod，而是使用 Guaranteed Pod，来避免进入驱逐候选列表。&lt;/p&gt;

&lt;h2 id=&#34;弃用的现存回收磁盘的选项&#34;&gt;弃用的现存回收磁盘的选项&lt;/h2&gt;

&lt;p&gt;为了保证节点的稳定性，Kubelet 已经尝试来释放磁盘空间了。&lt;/p&gt;

&lt;p&gt;因为基于磁盘的驱逐方式已经成熟，下列的 Kubelet 参数会被标记为弃用。&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;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;ndash;image-gc-high-threshold&lt;/td&gt;
&lt;td&gt;&amp;ndash;eviction-hard or eviction-soft&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&amp;ndash;image-gc-low-threshold&lt;/td&gt;
&lt;td&gt;&amp;ndash;eviction-minimum-reclaim&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&amp;ndash;maximum-dead-containers&lt;/td&gt;
&lt;td&gt;弃用&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&amp;ndash;maximum-dead-containers-per-container&lt;/td&gt;
&lt;td&gt;弃用&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&amp;ndash;minimum-container-ttl-duration&lt;/td&gt;
&lt;td&gt;弃用&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&amp;ndash;low-diskspace-threshold-mb&lt;/td&gt;
&lt;td&gt;&amp;ndash;eviction-hard or eviction-soft&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&amp;ndash;outofdisk-transition-frequency&lt;/td&gt;
&lt;td&gt;&amp;ndash;eviction-pressure-transition-period&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&#34;已知问题&#34;&gt;已知问题&lt;/h2&gt;

&lt;h3 id=&#34;kubelet-无法及时观测到内存压力&#34;&gt;Kubelet 无法及时观测到内存压力&lt;/h3&gt;

&lt;p&gt;Kubelet 目前从 cAdvisor 定时获取内存使用状况统计。如果内存使用在这个时间段内发生了快速增长，Kubelet 就无法观察到 MemoryPressure，可能会触发 OOMKiller。我们正在尝试将这一过程集成到 memcg 通知 API 中，来降低这一延迟，而不是让内核首先发现这一情况。&lt;/p&gt;

&lt;p&gt;如果用户不是希望获得终极使用率，而是作为一个过量使用的衡量方式，对付这一个问题的较为可靠的方式就是设置驱逐阈值为 75% 容量。这样就提高了避开 OOM 的能力，提高了驱逐的标准，有助于集群状态的平衡。&lt;/p&gt;

&lt;h3 id=&#34;kubelet-可能驱逐超出需要的更多-pod&#34;&gt;Kubelet 可能驱逐超出需要的更多 Pod&lt;/h3&gt;

&lt;p&gt;这也是因为状态搜集的时间差导致的。未来会加入功能，让根容器的统计频率和其他容器分别开来（&lt;code&gt;https://github.com/google/cadvisor/issues/1247&lt;/code&gt;）。&lt;/p&gt;

&lt;h3 id=&#34;kubelet-如何在-inode-耗尽的时候评价-pod-的驱逐&#34;&gt;Kubelet 如何在 inode 耗尽的时候评价 Pod 的驱逐&lt;/h3&gt;

&lt;p&gt;目前不可能知道一个容器消耗了多少 inode。如果 Kubelet 觉察到了 inode 耗尽，他会利用 QoS 对 Pod 进行驱逐评估。在 &lt;a href=&#34;https://github.com/google/cadvisor/issues/1422&#34; target=&#34;_blank&#34;&gt;cadvisor 中有一个 issue&lt;/a&gt;，来跟踪容器的 inode 消耗，这样我们就能利用 inode 进行评估了。例如如果我们知道一个容器创建了大量的 0 字节文件，就会优先驱逐这一 Pod&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 kubectl 中使用 Service Account Token</title>
      <link>/post/account-token-for-kubectl/</link>
      <pubDate>Thu, 11 May 2017 23:27:37 +0800</pubDate>
      <guid>/post/account-token-for-kubectl/</guid>
      <description>

&lt;p&gt;在运行基于 K8S 的 CI/CD 过程中，经常有需求在容器中对 Kubernetes 的资源进行操作，其中隐藏的安全问题，目前推荐的最佳实践也就是使用 Service Account 了。而调试账号能力的最好方法，必须是 kubectl 了。下面就讲讲如何利用 kubectl 引用 Servie Account 凭据进行 K8S 操作的方法。&lt;/p&gt;

&lt;p&gt;这里用 default Service Account 为例。&lt;/p&gt;

&lt;h2 id=&#34;假设&#34;&gt;假设&lt;/h2&gt;

&lt;p&gt;目前已经能对目标集群进行操作，文中需要的权限主要就是读取命名空间中的 Secret 和 Service Account。&lt;/p&gt;

&lt;h2 id=&#34;准备配置文件&#34;&gt;准备配置文件&lt;/h2&gt;

&lt;p&gt;新建一个 Yaml 文件，命名请随意，例如 kubectl.yaml。内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority-data: {ca data}
    server: https://{server}
  name: awesome-cluster
users:
- user:
    token: {token}
  name: account
- context:
    cluster: awesome-cluster
    user: account
  name: sa
current-context: sa
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中的 {ca data} 可以从现有连接凭据中获取。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;{server}&lt;/code&gt;：服务器地址&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;{token}&lt;/code&gt;：将在后面设置&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;获取数据&#34;&gt;获取数据&lt;/h2&gt;

&lt;p&gt;首先查看 Service Account 的 Token 在哪里：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get serviceaccount default -o yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;返回内容如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: 2017-05-07T10:41:50Z
  name: default
  namespace: default
  resourceVersion: &amp;quot;26&amp;quot;
  selfLink: /api/v1/namespaces/default/serviceaccounts/default
  uid: c715217d-3311-11e7-a4ae-42010a8c0095
secrets:
- name: default-token-7h4bd
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里我们看到他包含了 secret: &amp;ldquo;default-token-7h4bd&amp;rdquo;，获取其中的内容：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get secret default-token-7h4bd -o yaml&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
data:
  ca.crt: [ca data]
  namespace: ZGVmYXVsdA==
  token: [token data]
  kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: default
    kubernetes.io/service-account.uid: c715217d-3311-11e7-a4ae-42010a8c0095
  creationTimestamp: 2017-05-07T10:41:50Z
  name: default-token-7h4bd
  namespace: default
  resourceVersion: &amp;quot;24&amp;quot;
  selfLink: /api/v1/namespaces/default/secrets/default-token-7h4bd
  uid: c71cc72d-3311-11e7-a4ae-42010a8c0095
type: kubernetes.io/service-account-token
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面 Token Data 内容就是我们需要的认证 Token 了&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;export my_token=&amp;quot;[tokendata]&amp;quot;
kubectl --kubeconfig=kubectl.yaml \
config set-credentials account \
--token=`echo ${tokendata} | base64 -D`
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样就把 Service Account 的 Token 取出来，并保存在 kubectl.yaml 中。利用这一配置文件就可以凭 Service Account 的身份来执行 kubectl 指令了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>kubeadm 安装 Kubernetes 1.6.2</title>
      <link>/post/kubeadm-install-k8s/</link>
      <pubDate>Mon, 08 May 2017 19:20:39 +0800</pubDate>
      <guid>/post/kubeadm-install-k8s/</guid>
      <description>

&lt;p&gt;因为一些莫可名状的原因，国内网络使用 Kubeadm 颇有难度，这里大概说一下过程中的一些坑。&lt;/p&gt;

&lt;p&gt;主体流程遵循官网指南：&lt;strong&gt;&lt;a href=&#34;https://kubernetes.io/docs/getting-started-guides/kubeadm/&#34; target=&#34;_blank&#34;&gt;https://kubernetes.io/docs/getting-started-guides/kubeadm/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&#34;1-4-准备工作&#34;&gt;&lt;sup&gt;1&lt;/sup&gt;&amp;frasl;&lt;sub&gt;4&lt;/sub&gt; 准备工作&lt;/h3&gt;

&lt;p&gt;这里用包管理的方式安装 kubeadm、Docker 等组件。需要注意一点点的是，如果用的非 Root 用户，要注意 sudo 的时候的环境代理设置问题。或者干脆在 apt/yum 的配置文件中写入代理服务器。&lt;/p&gt;

&lt;p&gt;另外这里安装 Docker 之后，注意给 Docker 配置代理。或者可以直接想法子搞到下面列表中的镜像，并导入 Docker 之中：&lt;/p&gt;

&lt;h3 id=&#34;镜像准备&#34;&gt;镜像准备&lt;/h3&gt;

&lt;p&gt;下面提到的镜像基于目前的 1.6.2 版本，每次更新都会有不同。&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Ver&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/kube-proxy-amd64&lt;/td&gt;
&lt;td&gt;v1.6.2&lt;/td&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/kube-controller-manager-amd64&lt;/td&gt;
&lt;td&gt;v1.6.2&lt;/td&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/kube-apiserver-amd64&lt;/td&gt;
&lt;td&gt;v1.6.2&lt;/td&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/kube-scheduler-amd64&lt;/td&gt;
&lt;td&gt;v1.6.2&lt;/td&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/etcd-amd64&lt;/td&gt;
&lt;td&gt;3.0.17&lt;/td&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/pause-amd64&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/k8s-dns-sidecar-amd64&lt;/td&gt;
&lt;td&gt;1.14.1&lt;/td&gt;
&lt;td&gt;DNS&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/k8s-dns-kube-dns-amd64&lt;/td&gt;
&lt;td&gt;1.14.1&lt;/td&gt;
&lt;td&gt;DNS&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64&lt;/td&gt;
&lt;td&gt;1.14.1&lt;/td&gt;
&lt;td&gt;DNS&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;gcr.io/google_containers/etcd&lt;/td&gt;
&lt;td&gt;2.2.1&lt;/td&gt;
&lt;td&gt;Calico&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;quay.io/calico/node&lt;/td&gt;
&lt;td&gt;v1.1.3&lt;/td&gt;
&lt;td&gt;Calico&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;quay.io/calico/cni&lt;/td&gt;
&lt;td&gt;v1.8.0&lt;/td&gt;
&lt;td&gt;Calico&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;quay.io/calico/kube-policy-controller&lt;/td&gt;
&lt;td&gt;v0.5.4&lt;/td&gt;
&lt;td&gt;Calico&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&#34;2-4-master-初始化&#34;&gt;&lt;sup&gt;2&lt;/sup&gt;&amp;frasl;&lt;sub&gt;4&lt;/sub&gt; Master 初始化&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kubeadm init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这里的几个问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;如果准备采用 flannel 插件，需要 &lt;code&gt;-pod-network-cidr 10.244.0.0/16&lt;/code&gt; 参数&lt;/li&gt;
&lt;li&gt;Ubuntu 下可能会出现无限等待 Node 加入的情况，可能的原因是 /etc/resolv.conf 中有关于 localhost 的 search 记录，删掉这行就能解决。&lt;/li&gt;
&lt;li&gt;如果准备采用 Calico 插件，那么要注意 Calico 的 YAML 中的 ip pool 跟集群设置一致。&lt;/li&gt;
&lt;li&gt;如果不是预先准备好的备份，过程会比较长，耐心。。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Init 成功之后会出一堆文本信息，其中包含几点内容，最好保存下来：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;自动生成的 kubectl config 文件，可以复制到 ~/.kube/config 中作为缺省 kubectl 使用。&lt;/li&gt;
&lt;li&gt;把新节点加入集群的方法。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外这里还有个提示——没有安装 Pod Network。&lt;/p&gt;

&lt;h2 id=&#34;3-4-安装-pod-network&#34;&gt;&lt;sup&gt;3&lt;/sup&gt;&amp;frasl;&lt;sub&gt;4&lt;/sub&gt; 安装 Pod Network&lt;/h2&gt;

&lt;p&gt;这一步很重要，所有的应用包括 Kube DNS 在内，都依赖于此。&lt;/p&gt;

&lt;p&gt;如上文所示，准备使用的是 Calico，这里值得注意的一坑是，不同版本的 Kubernetes 会有不同的 Calico 安装，1.6 版本应该使用的是：&lt;code&gt;kubectl apply -f http://docs.projectcalico.org/v2.1/getting-started/kubernetes/installation/hosted/kubeadm/1.6/calico.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://docs.projectcalico.org/v2.1/getting-started/kubernetes/installation/hosted/kubeadm/&lt;/code&gt; 还有一些其他的安装方式可以参考。&lt;/p&gt;

&lt;h3 id=&#34;calico-的注意事项&#34;&gt;Calico 的注意事项&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;他的 ETCD 服务器要求装在 Master 所在的 Node 上，使用的是 nodeSelector 的方式，选择标签：&lt;code&gt;node-role.kubernetes.io/master: &amp;quot;&amp;quot;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;上文提到的 pod network cidr 和 ip pool 的一致性问题&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--service-cidr&lt;/code&gt; 和 IP Pool 不能重叠&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;4-4-加入节点&#34;&gt;&lt;sup&gt;4&lt;/sup&gt;&amp;frasl;&lt;sub&gt;4&lt;/sub&gt; 加入节点&lt;/h3&gt;

&lt;p&gt;在其他 Node 上做好准备工作之后，用&lt;code&gt;kubeadm init&lt;/code&gt;命令中提到的&lt;code&gt;kubeadmin join&lt;/code&gt;方法加入集群。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 的高级调度</title>
      <link>/post/adv-scheduler-in-k8s/</link>
      <pubDate>Sat, 06 May 2017 09:33:47 +0800</pubDate>
      <guid>/post/adv-scheduler-in-k8s/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2017/03/advanced-scheduling-in-kubernetes&#34; target=&#34;_blank&#34;&gt;Advanced Scheduling in Kubernetes
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 的调度器能够满足绝大多数要求，例如保证 Pod 只在资源足够的节点上运行，会尝试把同一个集合的 Pod 分散在不同的节点上，还会尝试平衡不同节点的资源使用率等。&lt;/p&gt;

&lt;p&gt;不过有时候你希望控制 Pod 的调度。例如你希望确认某个 Pod 只运行在有特定硬件的节点上；或者想要让频繁互相通信的服务能就近部署；又或者你希望用独立的节点给部分用户提供服务。而且最终，用户总是比 Kubernetes 更了解自己的应用。&lt;/p&gt;

&lt;p&gt;所以 Kubernetes 1.6 提供了四个高级调度功能：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;节点亲和/互斥&lt;/li&gt;
&lt;li&gt;Taint（污染、变质） 和 Tolerations（容忍、耐受）&lt;/li&gt;
&lt;li&gt;Pod 的亲和/互斥&lt;/li&gt;
&lt;li&gt;以及自定义调度&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上述功能在 Kubernetes 1.6 中还属于 Beta 阶段。&lt;/p&gt;

&lt;h2 id=&#34;节点亲和-互斥&#34;&gt;节点亲和/互斥&lt;/h2&gt;

&lt;p&gt;节点的亲和和互斥是一种设置调度器选择节点的规则。这个规则是 nodeSelector（1.0 开始就有的功能）的衍生物。这一规则使用类似给 Node 添加自定义标签，在 Pod 中定义选择器的方式。规则在调度器中可以有必要和推荐两种级别。&lt;/p&gt;

&lt;p&gt;必要的规则要求 Pod 必须调度到某指定节点上。如果没有符合条件（当然也包括通用的调度要求，例如节点必须有足够的资源）。如果没有符合要求的节点，Pod 就不会被调度，必要规则在 &lt;code&gt;nodeAffinity&lt;/code&gt; 的 &lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt; 字段中定义。&lt;/p&gt;

&lt;p&gt;例如在一个 GCE 上的多区域 Kubernetes 集群中，我们希望把 Pod 运行在一个 &lt;code&gt;us-central1-a&lt;/code&gt; 的区域中，我们可以在 Pod 中使用如下的亲和规则：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
          - key: &amp;quot;failure-domain.beta.kubernetes.io/zone&amp;quot;
            operator: In
            values: [&amp;quot;us-central1-a&amp;quot;]

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;IgnoredDuringExecution&lt;/code&gt; 表示在 Pod 已经成功运行后，如果 Node 的标签发生了变化导致其不再符合 Pod 的调度要求，Pod 依然会继续运行；&lt;code&gt;requiredDuringSchedulingRequiredDuringExecution&lt;/code&gt; 则相反，一旦出现这种变化，他会立即从 Node 上驱逐 Pod。&lt;/p&gt;

&lt;p&gt;推荐级别的规则表示优先选择符合规则要求的节点，如果找不到，则降级选择普通节点。我们可以用优先规则代替必要规则，选择&lt;code&gt;us-central1-a&lt;/code&gt;进行 Pod 的运行，只要修改成&lt;code&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
affinity:
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
          - key: &amp;quot;failure-domain.beta.kubernetes.io/zone&amp;quot;
            operator: In
            values: [&amp;quot;us-central1-a&amp;quot;]

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;节点的互斥可以利用否定操作符来实现。所以如果让 Pod 避免运行在&lt;code&gt;us-central1-a&lt;/code&gt;，可以这样实现：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
          - key: &amp;quot;failure-domain.beta.kubernetes.io/zone&amp;quot;
            operator: NotIn
            values: [&amp;quot;us-central1-a&amp;quot;]

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可用的操作符包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In&lt;/li&gt;
&lt;li&gt;NotIn&lt;/li&gt;
&lt;li&gt;Exists&lt;/li&gt;
&lt;li&gt;DoesNotExist&lt;/li&gt;
&lt;li&gt;Gt&lt;/li&gt;
&lt;li&gt;Lt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;需要这一功能的场景还包括节点的硬件结构、操作系统版本或者特殊硬件等。节点的亲和与互斥在 Kubernetes 1.6 之中处于 Beta 阶段。&lt;/p&gt;

&lt;h2 id=&#34;taint-污染-变质-和-tolerations-容忍-耐受&#34;&gt;Taint（污染、变质）和 Tolerations（容忍、耐受）&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;这俩名词让我非常挠头，不好下嘴。&lt;/p&gt;

&lt;p&gt;另外这里的阐述比起 Kubectl help taint 来说，清晰程度差了太多。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这一功能让用户可以把一个节点标记为 taint 的话，除非 Pod 被标识为可以耐受污染节点，否则不会有任何 Pod 被调度到该节点上。之所以把 taint 标记到节点而不是像亲和性一样标记在 Pod 上，是因为在这种情况下，绝大多数的 Pod 都不应该部署到 Taint 的节点上。例如用户可能希望把主节点保留给 Kubernetes 系统组件使用，或者把一部分节点保留给一组用户，或者把一组具有特殊硬件的服务器保留给有需求的 Pod。&lt;/p&gt;

&lt;p&gt;可以用 &lt;code&gt;kubectl&lt;/code&gt; 命令对节点进行 taint 操作：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl taint nodes node1 key=value:NoSchedule&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;在节点上创建了一个 tiant，一个 Pod 必须在 Spec 中做出这样的 Toleration 定义，才能调度到该节点：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
tolerations: 
- key: &amp;quot;key&amp;quot;
  operator: &amp;quot;Equal&amp;quot;
  value: &amp;quot;value&amp;quot;
  effect: &amp;quot;NoSchedule&amp;quot;

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;effect 除了 NoSchedule 这个值之外，还有一个 Prefer 版本的 &lt;code&gt;PreferNoSchedule&lt;/code&gt;，另外还有一个 &lt;code&gt;NoExecute&lt;/code&gt; 选项，这个选项意味着这一 Taint 生效之时，如果该节点内正在运行的 Pod 没有对应的 Tolerate 设置，会被直接逐出。&lt;/p&gt;

&lt;p&gt;目前这一特性在 Kubernetes 1.6 升级为 Beta，我们加入了一个 Alpha 特性，可以指定在节点遇到问题的时候，该节点之上的 Pod 可以保持该绑定的时间长度（缺省五分钟）。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;参考：
&lt;a href=&#34;https://kubernetes.io/docs/user-guide/node-selection/#per-pod-configurable-eviction-behavior-when-there-are-node-problems-alpha-feature&#34; target=&#34;_blank&#34;&gt;https://kubernetes.io/docs/user-guide/node-selection/#per-pod-configurable-eviction-behavior-when-there-are-node-problems-alpha-feature&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;pod-的亲和与互斥&#34;&gt;Pod 的亲和与互斥&lt;/h2&gt;

&lt;p&gt;Node 的亲和与互斥特性允许用户通过对 Pod 的定义来选择运行的 Node。但是还有一种需求就是，Pod 的相互关系，例如对同一个服务里面的 Pod 进行分布或者集中，或者和其他服务的 Pod 如何相处？Pod 的亲和与互斥就应运而生，这一特性在 Kubernetes 1.6 中也处于 Beta 阶段。&lt;/p&gt;

&lt;p&gt;看一个例子。假设有一个叫 S1 的前端服务，会经常和一个叫 S2 的后端服务进行通信（南北通信模式），所以我们希望这两个服务能够被安排在同一个云服务区域，但是我们也不想做手工选择——一旦某个区域出了问题，我们希望这些 Pod 能够再次迁移到同一个区域。这里就可以定义 Pod 亲和性来达成这一目的了（假设我们给两组服务都设置 Label，service=s1/s2）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: service
            operator: In
            values: [&amp;quot;S1&amp;quot;]
        topologyKey: failure-domain.beta.kubernetes.io/zone

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;和节点的亲和性类似，这里也有一个变量：&lt;code&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;Pod 的亲和性弹性很大。设想在性能测试的过程中，发现两个服务的容器处于同一个节点时，S1 的容器会干扰 S2 的容器的性能，这可能会是由缓存或者网络的拥堵造成的。或者出于安全考虑，我们不想两个服务共享同一个节点。要实现这种互斥操作，只要稍微改动一下哎上面的代码：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;podAffinity&lt;/code&gt; 改为 &lt;code&gt;podAntiAffinity&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;topologyKey&lt;/code&gt; 改为 &lt;code&gt;kubernetes.io/hostname&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;自定义调度&#34;&gt;自定义调度&lt;/h2&gt;

&lt;p&gt;如果 Kubernetes 调度器的众多特性还没能满足你的控制欲，可以用自己独立运行的调度器来对指定的 Pod 进行调度。在 Kubernetes 1.6 中，多调度器特性也进入了 Beta 阶段。&lt;/p&gt;

&lt;p&gt;一般情况下，每个新 Pod 都会由缺省调度器进行调度。但是如果 Pod 中提供了自定义的调度器名称，那么缺省调度器就会忽略该 Pod，转由指定的调度器把该 Pod 分配给节点。下面举例说明。&lt;/p&gt;

&lt;p&gt;代码中的 Pod 指定了&lt;code&gt;schedulerName&lt;/code&gt;字段：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  schedulerName: my-scheduler
  containers:
  - name: nginx
    image: nginx:1.10

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果我们在不部署自定义调度器的情况下，创建这个 Pod，缺省调度器会忽略这个 Pod，后果是他会在&lt;code&gt;Pending&lt;/code&gt;状态下停滞不前。所以我们需要为他创建一个&lt;code&gt;schedulerName&lt;/code&gt;值为&lt;code&gt;my-scheduler&lt;/code&gt;的调度器。&lt;/p&gt;

&lt;p&gt;可以用任何语言来实现简单或复杂的调度器。下面的简单例子是用 Bash 实现的——随机指派一个节点。注意首先要运行&lt;code&gt;kubectl proxy&lt;/code&gt;来支持这一脚本的运行。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/bash
SERVER=&#39;localhost:8001&#39;
while true;
do
    for PODNAME in $(kubectl --server $SERVER get pods -o json | jq &#39;.items[] | select(.spec.schedulerName == &amp;quot;my-scheduler&amp;quot;) | select(.spec.nodeName == null) | .metadata.name&#39; | tr -d &#39;&amp;quot;&#39;)
;
    do
        NODES=($(kubectl --server $SERVER get nodes -o json | jq &#39;.items[].metadata.name&#39; | tr -d &#39;&amp;quot;&#39;))
        NUMNODES=${#NODES[@]}
        CHOSEN=${NODES[$[ $RANDOM % $NUMNODES ]]}
        curl --header &amp;quot;Content-Type:application/json&amp;quot; --request POST --data &#39;{&amp;quot;apiVersion&amp;quot;:&amp;quot;v1&amp;quot;, &amp;quot;kind&amp;quot;: &amp;quot;Binding&amp;quot;, &amp;quot;metadata&amp;quot;: {&amp;quot;name&amp;quot;: &amp;quot;&#39;$PODNAME&#39;&amp;quot;}, &amp;quot;target&amp;quot;: {&amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;, &amp;quot;kind&amp;quot;
: &amp;quot;Node&amp;quot;, &amp;quot;name&amp;quot;: &amp;quot;&#39;$CHOSEN&#39;&amp;quot;}}&#39; http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
        echo &amp;quot;Assigned $PODNAME to $CHOSEN&amp;quot;
    done
    sleep 1
done

&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;更多&#34;&gt;更多&lt;/h2&gt;

&lt;p&gt;Kubernetes 1.6 的 release notes 中提供了更多的这些特性的相关信息，其中尤其包括了如果已经使用了 Alpha 版本如何进行升级的问题。&lt;/p&gt;

&lt;h2 id=&#34;鸣谢&#34;&gt;鸣谢&lt;/h2&gt;

&lt;p&gt;文中描写的功能，包括 Alpha 和 Beta 阶段的功能，都是社区中来自 Google、华为、IBM 以及 Red Hat 等公司的工程师的努力成果。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>GlusterFS &#43; Heketi 入门（非容器）</title>
      <link>/post/glusterfs-and-heketi-on-physical-server/</link>
      <pubDate>Wed, 19 Apr 2017 19:05:50 +0800</pubDate>
      <guid>/post/glusterfs-and-heketi-on-physical-server/</guid>
      <description>

&lt;p&gt;GlusterFS 是个开源的分布式文件系统，而 Heketi 在其上提供了 REST 形式的 API，二者协同为 Kubernetes 提供了存储卷的自动供给能力。&lt;/p&gt;

&lt;p&gt;一般对这个系统的介绍，都是基于 Docker 的容器内完成的，个人爱好原因，还不太习惯把这个事情放到集群里面，所以介绍一下用 Yum 方式的安装过程。&lt;/p&gt;

&lt;p&gt;我们使用三台服务器作为存储集群，操作系统为 CentOS 7。另外假设每台 Gluster FS 服务器挂在有名为 /dev/sdc 的裸设备，安装过程需要有互联网连接。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heketi 服务器：10.211.55.31&lt;/li&gt;
&lt;li&gt;Gluster 服务器：

&lt;ul&gt;
&lt;li&gt;10.211.55.31&lt;/li&gt;
&lt;li&gt;10.211.55.32&lt;/li&gt;
&lt;li&gt;10.211.55.33&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;heketi-的安装和初始设置&#34;&gt;Heketi 的安装和初始设置&lt;/h2&gt;

&lt;p&gt;这个很简单，CentOS 的 EPEL Repository 中就提供了他的安装包。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;yum install -y heketi heketi-client
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;安装之后，会生成 Heketi 的 Service，建立 /etc/heketi，并在其中生成一个叫 heketi.json 的配置文件。这里提供一个样本：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;{
  &amp;quot;port&amp;quot;: &amp;quot;7070&amp;quot;,
  &amp;quot;use_auth&amp;quot;: false,
  &amp;quot;jwt&amp;quot;: {
    &amp;quot;admin&amp;quot;: {
      &amp;quot;key&amp;quot;: &amp;quot;My Secret&amp;quot;
    },
    &amp;quot;user&amp;quot;: {
      &amp;quot;key&amp;quot;: &amp;quot;My Secret&amp;quot;
    }
  },
  &amp;quot;glusterfs&amp;quot;: {
    &amp;quot;executor&amp;quot;: &amp;quot;ssh&amp;quot;,
    &amp;quot;sshexec&amp;quot;: {
      &amp;quot;keyfile&amp;quot;: &amp;quot;/etc/heketi/heketi_key&amp;quot;,
      &amp;quot;user&amp;quot;: &amp;quot;root&amp;quot;,
      &amp;quot;port&amp;quot;: &amp;quot;22&amp;quot;,
      &amp;quot;fstab&amp;quot;: &amp;quot;/etc/fstab&amp;quot;
    },
    &amp;quot;executor&amp;quot;: &amp;quot;ssh&amp;quot;,
    &amp;quot;db&amp;quot;: &amp;quot;/var/lib/heketi/heketi.db&amp;quot;,
    &amp;quot;loglevel&amp;quot;: &amp;quot;debug&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个简单的配置文件说明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 7070 提供服务。&lt;/li&gt;
&lt;li&gt;数据库保存在 &lt;code&gt;/var/lib/heketi/heketi.db&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;关闭认证&lt;/li&gt;
&lt;li&gt;利用 ssh 和 GlusterFS 集群成员进行通信&lt;/li&gt;
&lt;li&gt;ssh 证书保存在 /etc/heketi/heketi_key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;既然提到证书，就用 ssh-keygen 来生成一套：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ssh-keygen -t rsa -q -f /etc/heketi/heketi_key -N &#39;&#39;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;后面将会使用这套证书来完成对 GlusterFS 的控制。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意，这里要保证上面提到的&lt;strong&gt;数据库、配置以及证书文件&lt;/strong&gt;，一定要确认 Heketi 用户有权进行访问。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;glusterfs-安装&#34;&gt;GlusterFS 安装&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;启用仓库：&lt;code&gt;yum install -y centos-release-gluster&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;安装软件：&lt;code&gt;yum install -y glusterfs-server&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;启用服务：&lt;code&gt;systemctl enable glusterfs-server&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;启动服务：&lt;code&gt;systemctl start glusterfs-server&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注意这里要把上个步骤生成的公钥（heketi_key.pub）加入到本机的信任列表中，例如&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cat /tmp/heketi_key.pub &amp;gt;&amp;gt; /root/.ssh/authorized_key&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&#34;集群初始化&#34;&gt;集群初始化&lt;/h2&gt;

&lt;p&gt;Heketi 对存储的拓扑结构是这样的：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;- Topology
    - Cluster a
        - Node a1
            - Device a11
            - Device a12
        - Node a2
    - Cluster b
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所以初始化过程就按照从上到下的方式来进行：&lt;/p&gt;

&lt;h3 id=&#34;建立集群&#34;&gt;建立集群&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;heketi-cli create cluster&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;创建成功后，会显示一个集群 ID。&lt;/p&gt;

&lt;h3 id=&#34;加入-node&#34;&gt;加入 Node&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;heketi-cli node add --cluster=[clusterid] \
--management-host-name=[node-host] \
--storage-host-name=[node-host] \
--zone=1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;运行成功会显示新加入的 Node 的 Node ID。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add Node 过程失败可能需要查看一下防火墙&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;加入-device&#34;&gt;加入 Device&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;heketi device add \
--name=/dev/sdc
--host=[host-id]
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;自动一点&#34;&gt;自动一点&lt;/h3&gt;

&lt;p&gt;下面的脚本会把运行参数中指定的第一参数作为主机地址，第二参数作为设备名称加入第一个集群&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
export HEKETI_CLI_SERVER=http://127.0.0.1:7070
CLUSTER_ID=`heketi-cli cluster list | tail -n 1 | xargs `
CLUSTER=&amp;quot;--cluster=$CLUSTER_ID&amp;quot;
HOST=&amp;quot;--management-host-name=$1 --storage-host-name=$1&amp;quot;
ZONE=&amp;quot;--zone=1&amp;quot;
NODE_ID=`heketi-cli node add $CLUSTER $HOST $ZONE | grep -v -i &amp;quot;Cluster&amp;quot; | grep -i &amp;quot;id&amp;quot; | cut -d : -f 2 | xargs`
heketi-cli device add --name=$2 --node=$NODE_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;命令需要用 &amp;lsquo;-s&amp;rsquo; 开关指定操作的 Heketi 服务地址。
可以用环境变量来简化一下： export HEKETI_CLI_SERVER=&amp;ldquo;&lt;a href=&#34;http://127.0.0.1:7070&amp;quot;。&#34; target=&#34;_blank&#34;&gt;http://127.0.0.1:7070&amp;quot;。&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;topology&#34;&gt;Topology&lt;/h2&gt;

&lt;p&gt;利用 &lt;code&gt;heketi-cli topology info&lt;/code&gt;，会输出当前的集群结构。而且也可以用 JSON 格式导入和导出整个 Topology。下面的例子供参考：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;volumes&amp;quot;: [],
    &amp;quot;nodes&amp;quot;: [{
      &amp;quot;zone&amp;quot;: 1,
      &amp;quot;hostnames&amp;quot;: {
        &amp;quot;manage&amp;quot;: [&amp;quot;10.211.55.19&amp;quot;],
        &amp;quot;storage&amp;quot;: [&amp;quot;10.211.55.19&amp;quot;]
      },
      &amp;quot;cluster&amp;quot;: &amp;quot;f6e6de7dc99ca3ed627e2ab3ae68f9ac&amp;quot;,
      &amp;quot;id&amp;quot;: &amp;quot;95d3d4fec82be4d2a55ae0aa17344af5&amp;quot;,
      &amp;quot;state&amp;quot;: &amp;quot;online&amp;quot;,
      &amp;quot;devices&amp;quot;: [{
        &amp;quot;name&amp;quot;: &amp;quot;/dev/sdc&amp;quot;,
        &amp;quot;storage&amp;quot;: {
          &amp;quot;total&amp;quot;: 33419264,
          &amp;quot;free&amp;quot;: 33419264,
          &amp;quot;used&amp;quot;: 0
        },
        &amp;quot;id&amp;quot;: &amp;quot;e4e1b97d38ed5ae70323458c1b8e57b5&amp;quot;,
        &amp;quot;state&amp;quot;: &amp;quot;online&amp;quot;,
        &amp;quot;bricks&amp;quot;: []
      }]
    }, {
      &amp;quot;zone&amp;quot;: 1,
      &amp;quot;hostnames&amp;quot;: {
        &amp;quot;manage&amp;quot;: [&amp;quot;10.211.55.21&amp;quot;],
        &amp;quot;storage&amp;quot;: [&amp;quot;10.211.55.21&amp;quot;]
      },
      &amp;quot;cluster&amp;quot;: &amp;quot;f6e6de7dc99ca3ed627e2ab3ae68f9ac&amp;quot;,
      &amp;quot;id&amp;quot;: &amp;quot;ab36d04dbface40904a05c33f3fd9800&amp;quot;,
      &amp;quot;state&amp;quot;: &amp;quot;online&amp;quot;,
      &amp;quot;devices&amp;quot;: [{
        &amp;quot;name&amp;quot;: &amp;quot;/dev/sdc&amp;quot;,
        &amp;quot;storage&amp;quot;: {
          &amp;quot;total&amp;quot;: 33419264,
          &amp;quot;free&amp;quot;: 33419264,
          &amp;quot;used&amp;quot;: 0
        },
        &amp;quot;id&amp;quot;: &amp;quot;a33dee6fd8355c6aa9ff5e2783ecef49&amp;quot;,
        &amp;quot;state&amp;quot;: &amp;quot;online&amp;quot;,
        &amp;quot;bricks&amp;quot;: []
      }]
    }, {
      &amp;quot;zone&amp;quot;: 1,
      &amp;quot;hostnames&amp;quot;: {
        &amp;quot;manage&amp;quot;: [&amp;quot;10.211.55.20&amp;quot;],
        &amp;quot;storage&amp;quot;: [&amp;quot;10.211.55.20&amp;quot;]
      },
      &amp;quot;cluster&amp;quot;: &amp;quot;f6e6de7dc99ca3ed627e2ab3ae68f9ac&amp;quot;,
      &amp;quot;id&amp;quot;: &amp;quot;bfd478cb0a0a562386c06967fb2b31bc&amp;quot;,
      &amp;quot;state&amp;quot;: &amp;quot;online&amp;quot;,
      &amp;quot;devices&amp;quot;: [{
        &amp;quot;name&amp;quot;: &amp;quot;/dev/sdc&amp;quot;,
        &amp;quot;storage&amp;quot;: {
          &amp;quot;total&amp;quot;: 33419264,
          &amp;quot;free&amp;quot;: 33419264,
          &amp;quot;used&amp;quot;: 0
        },
        &amp;quot;id&amp;quot;: &amp;quot;24c5a97ccad5b3fc35977bc7419c27ee&amp;quot;,
        &amp;quot;state&amp;quot;: &amp;quot;online&amp;quot;,
        &amp;quot;bricks&amp;quot;: []
      }]
    }],
    &amp;quot;id&amp;quot;: &amp;quot;f6e6de7dc99ca3ed627e2ab3ae68f9ac&amp;quot;
  }]
}
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的 RBAC 支持</title>
      <link>/post/rbac-in-kubernetes/</link>
      <pubDate>Sun, 09 Apr 2017 08:29:17 +0800</pubDate>
      <guid>/post/rbac-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2017/04/rbac-support-in-kubernetes&#34; target=&#34;_blank&#34;&gt;RBAC Support in Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;rbac-vs-abac&#34;&gt;RBAC vs ABAC&lt;/h2&gt;

&lt;p&gt;目前 Kubernetes 中有一系列的&lt;a href=&#34;https://kubernetes.io/docs/admin/authorization/&#34; target=&#34;_blank&#34;&gt;鉴权机制&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;鉴权的作用是，决定一个用户是否有权使用 Kubernetes API 做某些事情。它除了会影响 kubectl 等组件之外，还会对一些运行在集群内部并对集群进行操作的软件产生作用，例如使用了 Kubernetes 插件的 Jenkins，或者是利用 Kubernetes API 进行软件部署的 Helm。ABAC 和 RBAC 都能够对访问策略进行配置。&lt;/p&gt;

&lt;p&gt;ABAC（Attribute Based Access Control）本来是不错的概念，但是在 Kubernetes 中的实现比较难于管理和理解（怪我咯），而且需要对 Master 所在节点的 SSH 和文件系统权限，而且要使得对授权的变更成功生效，还需要重新启动 API Server。&lt;/p&gt;

&lt;p&gt;而 RBAC 的授权策略可以利用 kubectl 或者 Kubernetes API 直接进行配置。RBAC 可以授权给用户，让用户有权进行授权管理，这样就可以无需接触节点，直接进行授权管理。RBAC 在 Kubernetes 中被映射为 API 资源和操作。&lt;/p&gt;

&lt;p&gt;因为 Kubernetes 社区的投入和偏好，相对于 ABAC 而言，RBAC 是更好的选择。&lt;/p&gt;

&lt;h2 id=&#34;基础概念&#34;&gt;基础概念&lt;/h2&gt;

&lt;p&gt;需要理解 RBAC 一些基础的概念和思路，RBAC 是让用户能够访问 Kubernetes API 资源的授权方式。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/rbac-1.png&#34; alt=&#34;rbac 1&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在 RBAC 中定义了两个对象，用于描述在用户和资源之间的连接权限。&lt;/p&gt;

&lt;h3 id=&#34;角色&#34;&gt;角色&lt;/h3&gt;

&lt;p&gt;角色是一系列的权限的集合，例如一个角色可以包含读取 Pod 的权限和列出 Pod 的权限， ClusterRole 跟 Role 类似，但是可以在集群中到处使用（ Role 是 namespace 一级的）。&lt;/p&gt;

&lt;h3 id=&#34;角色绑定&#34;&gt;角色绑定&lt;/h3&gt;

&lt;p&gt;RoleBinding 把角色映射到用户，从而让这些用户继承角色在 namespace 中的权限。ClusterRoleBinding 让用户继承 ClusterRole 在整个集群中的权限。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/rbac-2.png&#34; alt=&#34;rbac 2&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/admin/authorization/rbac/#rolebinding-and-clusterrolebinding&#34; target=&#34;_blank&#34;&gt;关于 RoleBinding 和 ClusterRoleBinding&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;kubernetes-中的-rbac&#34;&gt;Kubernetes 中的 RBAC&lt;/h2&gt;

&lt;p&gt;RBAC 现在被 Kubernetes 深度集成，并使用他给系统组件进行授权。系统角色 (System Roles) 一般具有前缀&lt;code&gt;system:&lt;/code&gt;，很容易识别：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;➜  kubectl get clusterroles --namespace=kube-system
NAME                    KIND
admin                   ClusterRole.v1beta1.rbac.authorization.k8s.io
cluster-admin           ClusterRole.v1beta1.rbac.authorization.k8s.io
edit                    ClusterRole.v1beta1.rbac.authorization.k8s.io
kubelet-api-admin       ClusterRole.v1beta1.rbac.authorization.k8s.io
system:auth-delegator   ClusterRole.v1beta1.rbac.authorization.k8s.io
system:basic-user       ClusterRole.v1beta1.rbac.authorization.k8s.io
system:controller:attachdetach-controller ClusterRole.v1beta1.rbac.authorization.k8s.io
system:controller:certificate-controller ClusterRole.v1beta1.rbac.authorization.k8s.io
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;RBAC 系统角色已经完成足够的覆盖，让集群可以完全在 RBAC 的管理下运行。&lt;/p&gt;

&lt;p&gt;在 ABAC 到 RBAC 进行迁移的过程中，有些在 ABAC 集群中缺省开放的权限，在 RBAC 中会被视为不必要的授权，会对其进行降级。这种情况会影响到使用 Service Account 的负载。ABAC 配置中，从 Pod 中发出的请求会使用 Pod Token，API Server 会为其授予较高权限。例如下面的命令在 APAC 集群中会返回 JSON 结果，而在 RBAC 的情况下则会返回错误。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;➜  kubectl run nginx --image=nginx:latest
➜  kubectl exec -it $(kubectl get pods -o jsonpath=&#39;{.items[0].metadata.name}&#39;) bash
➜  apt-get update &amp;amp;&amp;amp; apt-get install -y curl
➜  curl -ik \
-H &amp;quot;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)&amp;quot; \
https://kubernetes/api/v1/namespaces/default/pods
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/admin/authorization/rbac/#upgrading-from-15&#34; target=&#34;_blank&#34;&gt;降级过程的说明&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所有在 Kubernetes 集群中运行的应用，一旦和 API Server 进行通信，都会有可能受到迁移的影响。&lt;/p&gt;

&lt;p&gt;要平滑的从 ABAC 升级到 RBAC，在创建 1.6 集群的时候，可以同时启用 ABAC 和 RBAC。当他们同时启用的时候，对一个资源的权限请求，在任何一方获得放行都会获得批准。然而在这种配置下的权限太过粗放，很可能无法在单纯的 RBAC 环境下工作。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/admin/authorization/rbac/#parallel-authorizers&#34; target=&#34;_blank&#34;&gt;RBAC 和 ABAC 同时运行&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在 Google Cloud Next 上的两次讲话提到了 Kubernetes 1.6 中的 RBAC。要获得更详细的信息，请阅读 RBAC 文档。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;https://www.youtube.com/watch?v=Cd4JU7qzYbE#t=8m01s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://www.youtube.com/watch?v=18P7cFc6nTU#t=41m06s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://kubernetes.io/docs/admin/authorization/rbac/&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>编写易移植的 PVC</title>
      <link>/post/portable-pvc/</link>
      <pubDate>Fri, 31 Mar 2017 06:33:10 +0800</pubDate>
      <guid>/post/portable-pvc/</guid>
      <description>&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/persistent-volumes/#writing-portable-configuration&#34; target=&#34;_blank&#34;&gt;Writing Portable Configuration&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果你在编写配置模板或者是一个可能在很多不同集群下运行的配置，要在其中包含持久存储，我们提供一些建议：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在配置包含 PVC 对象（和 Deployments、COnfigMap 等并列）。&lt;/li&gt;
&lt;li&gt;考虑到可能的权限限制，建议不要包含 PV 对象&lt;/li&gt;
&lt;li&gt;编写模板的话，建议给用户指定 Storage Class 的选项：

&lt;ul&gt;
&lt;li&gt;如果用户提供了一个 Storage Class，并且集群版本在 1.4 以上，把这个 Storage Class Name 放入 PVC 的 &lt;code&gt;volume.beta.kubernetes.io/storage-class&lt;/code&gt; 标注之中，如果集群中启用了指定的 Storage Class，PVC 就会成功匹配到相应资源。&lt;/li&gt;
&lt;li&gt;如果用户没有提供 Storage Class，或者集群版本为 1.3，那么就在 PVC 中添加 &lt;code&gt;volume.alpha.kubernetes.io/storage-class: default&lt;/code&gt; 标注。&lt;/li&gt;
&lt;li&gt;PV 会自动在集群中为用户提供平台指定的缺省 PV。&lt;/li&gt;
&lt;li&gt;虽然字面上是 alpha，但实际上提供的是 beta 级别的支持。&lt;/li&gt;
&lt;li&gt;不要使用&lt;code&gt;volume.beta.kubernetes.io/storage-class&lt;/code&gt;，任何值包括空字符串，都会阻止 DefaultStorageClass 控制的运行。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;如果 PVC 创建一段时间之后，还没能绑定，需要把这一信息呈现给用户，因为这可能是因为这个集群没有动态存储供给的支持（这种情况下，需要创建符合 PVC 要求的 PV），或者个集群没有存储系统（这种情况下就无法运行指定的 PVC 了）。&lt;/li&gt;
&lt;li&gt;未来我们希望让多数集群具备 DefaultStorageClass，并有提供某种可用的存储。然而，让一种 Storage Class 适应所有的集群是很难的，所以还是不建议直接在 PVC 中设置缺省的 Storage Class。Alpha 标注会在未来失效，但那时候未进行设置的 storageClass 字段就会实现期望的功能了（1.6）。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Helm 简介</title>
      <link>/post/helm-introducing/</link>
      <pubDate>Thu, 30 Mar 2017 08:47:48 +0800</pubDate>
      <guid>/post/helm-introducing/</guid>
      <description>

&lt;h2 id=&#34;概念&#34;&gt;概念&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Chart：一个 Helm 包，其中包含了运行一个应用所需要的工具、资源定义等，还可能包含 Kubernetes 集群中的服务定义，类似 Homebrew 中的 formula，APT 的 dpkg 或者 Yum 的 RPM 文件，&lt;/li&gt;
&lt;li&gt;Release: 在 Kubernetes 集群上运行的 Chart 的一个实例。在同一个集群上，一个 Chart 可以安装很多次。每次安装都会创建一个新的 release。例如一个 MySQL Chart，如果想在服务器上运行两个数据库，就可以把这个 Chart 安装两次。每次安装都会生成自己的 Release，会有自己的 Release 名称。&lt;/li&gt;
&lt;li&gt;Repository：用于存放和共享 Chart 的仓库。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;简单说来，Helm 整个系统的主要任务就是，在仓库中查找需要的 Chart，然后把 Chart 以 Release 的形式安装到 Kubernetes 之中&lt;/p&gt;

&lt;h2 id=&#34;组件&#34;&gt;组件&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Helm Client：客户端，具有对 Repository、Chart、Release 等对象的管理能力。&lt;/li&gt;
&lt;li&gt;Tiller Server：负责客户端指令和 Kubernetes 集群之间的沟通，根据 Chart 定义，生成和管理各种相对应的 API Object。&lt;/li&gt;
&lt;li&gt;Repository：Chart 的仓库，基本上就是索引文件 + Chart 压缩包的一个存储托管。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;

&lt;h3 id=&#34;环境要求&#34;&gt;环境要求&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;可用的 Kubernetes 集群&lt;/li&gt;
&lt;li&gt;正确的可用的 kubectl config&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;简单做法&#34;&gt;简单做法&lt;/h3&gt;

&lt;p&gt;只要一个简单的 &lt;code&gt;helm init&lt;/code&gt; 命令，就可以自动完成 Tiller 的安装，以及 Helm 本地设置。&lt;/p&gt;

&lt;h3 id=&#34;手工做法&#34;&gt;手工做法&lt;/h3&gt;

&lt;p&gt;在一个网络不通，或者受控网络里面，init 可能无法正常工作，所以我们需要手工完成 Init 工作。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;客户端：&lt;code&gt;helm init -c&lt;/code&gt;，会跳过 Tiller 部分，仅进行客户端的安装。

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~/.helm&lt;/code&gt; 中保存了对 Repository 的定义，各个 Repository 的索引的缓存，以及 Chart 压缩包的缓存。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Tiller：下面是 Tiller 的运行 YML：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: &amp;quot;1&amp;quot;
generation: 1
labels:
app: helm
name: tiller
name: tiller-deploy
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
  app: helm
  name: tiller
strategy:
rollingUpdate:
  maxSurge: 1
  maxUnavailable: 1
type: RollingUpdate
template:
metadata:
  creationTimestamp: null
  labels:
    app: helm
    name: tiller
spec:
  containers:
  - env:
    - name: TILLER_NAMESPACE
      value: kube-system
    image: gcr.io/kubernetes-helm/tiller:v2.2.3
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 3
      httpGet:
        path: /liveness
        port: 44135
        scheme: HTTP
      initialDelaySeconds: 1
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 1
    name: tiller
    ports:
    - containerPort: 44134
      name: tiller
      protocol: TCP
    readinessProbe:
      failureThreshold: 3
      httpGet:
        path: /readiness
        port: 44135
        scheme: HTTP
      initialDelaySeconds: 1
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 1
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  securityContext: {}
  terminationGracePeriodSeconds: 30
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Service&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Service
metadata:
  creationTimestamp: 2017-03-27T15:06:49Z
  labels:
    app: helm
    name: tiller
  name: tiller-deploy
  namespace: kube-system
spec:
  clusterIP: 10.3.251.197
  ports:
  - name: tiller
    port: 44134
    protocol: TCP
    targetPort: tiller
  selector:
    app: helm
    name: tiller
  sessionAffinity: None
  type: ClusterIP
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;注意楼上的 Service 代码表明 Tiller 并没有用 NodePort 或者 Ingress/Loadbalancer 等方式暴露端口，因此这里&lt;strong&gt;推测&lt;/strong&gt; Helm 同 Tiller 的通信可能是利用 kubectl proxy 完成的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;常用操作&#34;&gt;常用操作&lt;/h2&gt;

&lt;p&gt;下面列举了一个简单的安装数据库的操作过程：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;helm search mysql #搜索
helm inspect stable/mariadb #查看
helm install stable/mariadb #安装
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;很明显，上面的命令没有对 Chart 的而运行做任何指导，实际上没什么用，我们可以利用 &lt;code&gt;--set&lt;/code&gt; 或者 &lt;code&gt;--value&lt;/code&gt; 参数，来指定在我们 Inspect 命令中看到的变量的值，就可以完成对变量的设置了。&lt;/p&gt;

&lt;p&gt;除了利用简单的模板功能来生成和控制 pvc/secret 等各种对象之外， Chart 还具有很重要的依赖管理功能。&lt;/p&gt;

&lt;h2 id=&#34;自建-chart&#34;&gt;自建 Chart&lt;/h2&gt;

&lt;p&gt;简单的一个 &lt;code&gt;helm create&lt;/code&gt; 命令就可以初始化一个 chart。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;├── Chart.yaml # Chart 的文件主要信息
├── charts # 依赖的 Chart
├── templates # 模板目录，用 gotpl 编写 Kubernetes 运行 App 所需的各种对象定义
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml # 缺省值定义
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;编写完成之后，使用 &lt;code&gt;helm package&lt;/code&gt; 命令可以将 chart 打包为 tgz 格式。&lt;/p&gt;

&lt;p&gt;最后可以运行 &lt;code&gt;helm install ./some.chart.tgz&lt;/code&gt; 来运行本地的 Chart。&lt;/p&gt;

&lt;h2 id=&#34;自建-repository&#34;&gt;自建 Repository&lt;/h2&gt;

&lt;p&gt;很明显的，我们需要一个自建仓库，不过幸运的是，Helm 仓库并不像 Docker Image Registry 之类的东西那么麻烦。只要一个能相应 GET 请求并提供 YAML 和 TAR 文件的 HTTP 服务器就可以了。&lt;/p&gt;

&lt;p&gt;最简单的，可以用 &lt;code&gt;helm serve&lt;/code&gt; 命令就可以了：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;首先创建一个文件夹用于保存 Chart&lt;/li&gt;
&lt;li&gt;把上面我们 &lt;code&gt;helm package&lt;/code&gt; 生成的压缩包复制进去&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm serve --repo-path repo&lt;/code&gt; 就会运行一个简单的 HTTP Server 提供 Helm chart 仓库的服务了。&lt;/li&gt;
&lt;li&gt;运行&lt;code&gt;helm repo add some-repo http://repo-url&lt;/code&gt; 命令，添加仓库到系统中。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外可以利用 github 以及各种 HTTP 服务器来提供这一服务。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>简单的 Kubernetes Pod 日志查看工具 Kubetail</title>
      <link>/post/introducing-kubetail/</link>
      <pubDate>Sat, 11 Mar 2017 01:47:52 +0800</pubDate>
      <guid>/post/introducing-kubetail/</guid>
      <description>

&lt;p&gt;​传统来说，Kubernetes 环境下的日志都是靠 FluentD + ElasticSearch + Kibana 的组合实现的，这一组合的功能和强大，所以成为一个事实标准来使用，但是在一些比较简陋的测试集群中，或者不具备浏览器条件的自动化/控制台环境下，归并多个 Pod 的日志进行集中的查看和处理还是很有用的。&lt;/p&gt;

&lt;p&gt;Kubetail 是一个 Bash 脚本，功能类似 &lt;code&gt;kubectl -f logs pod-name&lt;/code&gt;，但是不同的是，他同时对多个 Pod 工作，并把日志合并到一个流中。&lt;/p&gt;

&lt;p&gt;项目网址：&lt;a href=&#34;https://github.com/johanhaleby/kubetail&#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;/p&gt;

&lt;h3 id=&#34;mac-用户&#34;&gt;Mac 用户：&lt;/h3&gt;

&lt;p&gt;brew tap johanhaleby/kubetail &amp;amp;&amp;amp; brew install kubetail&lt;/p&gt;

&lt;h3 id=&#34;使用&#34;&gt;使用&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;kubetail [-h] [-c] [-n] [-t] [-l] [-s] pod-name-prefix

-c：多容器 Pod 中，指定容器名称
-t：指定 kube config 文件中的 context
-l：标签过滤器，使用 -l 参数之后，会忽略后面的 pod 名称
-n：namespace
-s：只返回一个相对时间之后的日志，例如 5s，2m 或者 3h，缺省是 10s
-b：是否使用 line-buffered。缺省为 false
-k：输出的着色
pod：只给 pod 名称上色
line：整行上色（缺省）
false：不上色
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>借助 Calico，管窥 Kubernetes 网络策略</title>
      <link>/post/network-policy-basic-calico/</link>
      <pubDate>Thu, 09 Mar 2017 20:37:41 +0800</pubDate>
      <guid>/post/network-policy-basic-calico/</guid>
      <description>

&lt;p&gt;Kubernetes 提出了一系列 CXI 的标准容器接口，其中的 CNI 以插件方式支持多种网络。新增的 networkpolicy API 对象，提供了对网络策略的支持，本文以 Calico 为例，实际操作一个网络策略的创建和测试。&lt;/p&gt;

&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;一个 Kubernetes 集群&lt;/li&gt;
&lt;li&gt;Kubelet 和 API Server 都开启了 &lt;code&gt;--allow_privileged=true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Kubelet 指定使用 CNI ：&lt;code&gt;--network-plugin=cni&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;为了避免某些不可描述的网络设施的影响，建议下载几个镜像

&lt;ul&gt;
&lt;li&gt;quay.io/calico/node:v1.0.2&lt;/li&gt;
&lt;li&gt;calico/cni:v1.5.6&lt;/li&gt;
&lt;li&gt;calico/kube-policy-controller:v0.5.2&lt;/li&gt;
&lt;li&gt;calico/ctl:v1.0.2&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;运行-calico&#34;&gt;运行 Calico&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;下载 &lt;code&gt;http://docs.projectcalico.org/v2.0/getting-started/kubernetes/installation/hosted/calico.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果用私库镜像，需要修改其中的几个镜像地址&lt;/li&gt;
&lt;li&gt;修改 &lt;code&gt;data/etcd_endpoints&lt;/code&gt; 的数据为可访问的 etcd 的地址。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;kubectl create -f calico.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这里在 kube-system 中创建了一个 DaemonSet 和一个 Deployment，分别用于提供 CNI 支持和网络策略支持。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl get deployment,daemonset,svc --all-namespaces                                          [9:55:14]
NAMESPACE     NAME                              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
kube-system   deploy/calico-policy-controller   1         1         1            1           10h

NAMESPACE     NAME             DESIRED   CURRENT   READY     NODE-SELECTOR   AGE
kube-system   ds/calico-node   2         2         2         &amp;lt;none&amp;gt;          10h

NAMESPACE   NAME             CLUSTER-IP        EXTERNAL-IP   PORT(S)   AGE
default     svc/kubernetes   172.200.0.1       &amp;lt;none&amp;gt;        443/TCP   19h
default     svc/nginx        172.200.183.204   &amp;lt;none&amp;gt;        80/TCP    9h
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;网络策略&#34;&gt;网络策略&lt;/h2&gt;

&lt;p&gt;为测试效果，我们首先创建一个 Namespace&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl create ns policy&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;然后是 Nginx 部署和服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
kind: ReplicationController
apiVersion: v1
metadata:
  name: nginx
  labels:
    name: nginx
spec:
  replicas: 1
  selector:
    name: nginx
  template:
    metadata:
      labels:
        name: nginx
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
          protocol: TCP

---
kind: Service
apiVersion: v1
metadata:
  name: nginx
  labels:
    name: nginx
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  selector:
    name: nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后我们用 alpine 镜像测试一下对这一服务进行访问：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl run alpine --rm -it --image=alpine sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;运行成功后，在 Alpine 的 Shell 中输入：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wget -O - -T 5 http://nginx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;会出现 Nginx 的缺省页面的代码。&lt;/p&gt;

&lt;p&gt;接下来我们给 Default Namespace 加一个缺省拒绝访问的注解：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ kubectl annotate ns default &amp;quot;net.beta.kubernetes.io/network-policy={\&amp;quot;ingress\&amp;quot;: {\&amp;quot;isolation\&amp;quot;: \&amp;quot;DefaultDeny\&amp;quot;}}&amp;quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;重复测试过程，会发现超时错误。&lt;/p&gt;

&lt;p&gt;我们来创建一条策略：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: NetworkPolicy
apiVersion: extensions/v1beta1
metadata:
  name: access-nginx
spec:
  podSelector:
    matchLabels:
      run: nginx
  ingress:
    - from:
      - podSelector:
          matchLabels:
            access: &amp;quot;true&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;很容易理解，对于符合 &amp;ldquo;run=nginx&amp;rdquo; 的 Pod，只有 &amp;ldquo;access=true&amp;rdquo; 的 Pod 能够访问&lt;/p&gt;

&lt;p&gt;给 Alpine 带上标签重新运行：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl run alp --image=alpine --labels=&amp;quot;access=true&amp;quot; --rm -ti sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;重新 wget，会发现访问能力已经恢复。&lt;/p&gt;

&lt;p&gt;本文主要线索来自官方示例：&lt;code&gt;https://kubernetes.io/docs/getting-started-guides/network-policy/walkthrough/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;安装方法来自 Calico 官网。&lt;/p&gt;

&lt;p&gt;这只是一个很入门的介绍，后续会有更多进一步的尝试。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Jenkins：乘着 Kubernetes 的翅膀</title>
      <link>/post/jenkins-worknode-by-kubernetes/</link>
      <pubDate>Thu, 09 Mar 2017 07:38:10 +0800</pubDate>
      <guid>/post/jenkins-worknode-by-kubernetes/</guid>
      <description>

&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;

&lt;p&gt;Kubernetes + Docker 是一对有意思的组合，为微服务架构的落地，扫清了最后一公里的障碍，在符合企业 IT 治理需求的前提之下，为传统企业应用的平滑过渡提供了有效条件和方法。&lt;/p&gt;

&lt;p&gt;作为软件生产环节中重要组成部分的持续构建和发布过程，自然也要随势而动，这方面的老将 Jenkins 不但提供了用于构建、推送 Docker 镜像的插件，更提供了利用 Kubernetes 运行构建集群的能力。本文将利用一个简单的 Hello world 项目，来展示 Jenkins 的这一特性。&lt;/p&gt;

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

&lt;h3 id=&#34;kubernetes-集群安装&#34;&gt;Kubernetes 集群安装&lt;/h3&gt;

&lt;p&gt;要在 Kubernetes 集群完成下面所有的工作，因此首先进行集群的安装和配置，这方面可以参考 &lt;a href=&#34;http://kubernetes.io/docs/hellonode/&#34; target=&#34;_blank&#34;&gt;官方入门文档&lt;/a&gt;，如果英语不灵但动手排错能力强的话，也可以参考&lt;a href=&#34;http://blog.fleeto.us/content/centos-7-kubernetes-11x-docker-19x-zhuang-zhi-nan&#34; target=&#34;_blank&#34;&gt;已经过时的拙作&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;集群安装后，应该具有以下能力：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;集群具有受 DNS 支持的服务寻址能力；&lt;/li&gt;
&lt;li&gt;能够利用 Kubectl 或者其他方法发布容器应用的能力；&lt;/li&gt;
&lt;li&gt;私有镜像库，或者到 Docker Hub 的网络连接；&lt;/li&gt;
&lt;li&gt;Pause 镜像也自然是 Kubernetes 中运行应用的必须条件。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;jenkins-镜像&#34;&gt;Jenkins 镜像&lt;/h3&gt;

&lt;p&gt;本文例子采用自制的一个集成镜像为基础工具，该镜像集成了众多常用的 CI/CD 工具，另外同时还包含了 Jenkins 的 Master 和 Slave 两种模式，镜像托管在 &lt;a href=&#34;https://hub.docker.com/r/dustise/jenkins/&#34; target=&#34;_blank&#34;&gt;Docker Hub&lt;/a&gt;，源码可在 &lt;a href=&#34;https://github.com/fleeto/docker-jenkins&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt; 浏览和下载。&lt;/p&gt;

&lt;p&gt;如果具有直接连接 Docker Hub 的网络连接，则可无需理会；否则就需要下载镜像，并上传至私库。为行文方便，这里假设采用私库方式，镜像地址为 &lt;code&gt;10.211.55.5:5000/jenkins:2.7.4.5&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&#34;共享存储&#34;&gt;共享存储&lt;/h3&gt;

&lt;p&gt;因容器在集群中的运行状况未知，一般来说是需要为容器化应用提供共享存储服务的，本文中采用最简单的 NFS 方式，当然也可以使用官方支持的其他方式，例如 GlusterFS、Flocker 以及 Ceph 等等，具体支持能力可以参看&lt;a href=&#34;http://kubernetes.io/docs/user-guide/volumes/&#34; target=&#34;_blank&#34;&gt;官方说明&lt;/a&gt;，当然，如果只是学习测试，使用缺省的 Empty 格式亦可，或者偷懒使用 HostPath 方式结合限定 Node 运行的方式也能够完成任务。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文中的卷挂载方式很是粗糙，目前推荐的共享存储方式是&lt;a href=&#34;http://kubernetes.io/docs/user-guide/persistent-volumes/&#34; target=&#34;_blank&#34;&gt;持久卷（PV &amp;amp; PVC）&lt;/a&gt;方式，以提供更好的管控能力，中文可参考：&lt;a href=&#34;http://blog.fleeto.us/translation/persistent-volumes&#34; target=&#34;_blank&#34;&gt;Kubernetes 中的 Persistent Volumes&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;jenkins-master&#34;&gt;Jenkins Master&lt;/h2&gt;

&lt;h3 id=&#34;yaml&#34;&gt;YAML&lt;/h3&gt;

&lt;p&gt;首先要为我们的 Jenkins 镜像编写一个 Yaml ，用于提交到集群中运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: ReplicationController
apiVersion: v1
metadata:
  name: jenkins
  labels:
    name: jenkins
spec:
  replicas: 1
  selector:
    name: jenkins
  template:
    metadata:
      labels:
        name: jenkins
    spec:
      containers:
      - name: jenkins
        image: 10.211.55.5:5000/jenkins:2.7.4.5
        ports:
        - containerPort: 8080
          protocol: TCP
        - containerPort: 8081
          protocol: TCP
        volumeMounts:
          - name: jenkins
            mountPath: /data/jenkins
      volumes:
      - name: jenkins
        nfs:
          server: 10.211.55.5
          path: /var/data/nfs/ci/jenkins
          readOnly: false
---
kind: Service
apiVersion: v1
metadata:
  name: jenkins
  labels:
    name: jenkins
spec:
  type: NodePort
  ports:
  - protocol: TCP
    nodePort: 32502
    targetPort: 8080
    port: 8080
    name: web
  - protocol: TCP
    targetPort: 8081
    port: 8081
    name: service
  selector:
    name: jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的 YML 文件有几点需要注意&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;挂载：Jenkins 的所有插件、配置和工作文件都处于环境变量 &lt;code&gt;JENKINS_HOME&lt;/code&gt; 所示的路径中，因此我们这里利用 NFS 把位于 &lt;code&gt;10.211.55.5&lt;/code&gt; 上的  &lt;code&gt;/var/data/nfs/ci/jenkins&lt;/code&gt; 目录映射到容器的  &lt;code&gt;/data/jenkins&lt;/code&gt; 之中，让 Jenkins 获得持久化存储。&lt;/li&gt;
&lt;li&gt;端口：Jenkins 缺省运行需要 8080 端口对外提供 Web 界面。这里我们另外声明了一个 8081 端口，为集群内新建的 Slave 提供通信能力，可以看到，下面的 Service 定义中，仅仅使用 NodePort 方式暴露了一个 Web 端口&lt;/li&gt;
&lt;li&gt;时区：镜像提供了 TIMEZONE 环境变量，这一变量将会在运行时影响容器操作系统以及 Jenkins 的 JVM 的时区设置。这一变量缺省使用 &lt;code&gt;Asia/Shanghai&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JENKINS_MODE&lt;/code&gt;：用于指示 Jenkins 的运行模式，可选值为 &lt;code&gt;MASTER&lt;/code&gt; 或者 &lt;code&gt;SLAVE&lt;/code&gt;，缺省运行在 &lt;code&gt;MASTER&lt;/code&gt; 模式下。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;运行和配置&#34;&gt;运行和配置&lt;/h2&gt;

&lt;p&gt;接下来就可以使用 &lt;code&gt;kubectl create -f jenkins.yaml&lt;/code&gt; 来运行镜像了。执行之后可以刷 &lt;code&gt;kubectl get pods&lt;/code&gt; 来查看启动情况。&lt;/p&gt;

&lt;p&gt;这一 RC 的启动需要加载 NFS 卷，因此如果启动时间过长，可以使用 &lt;code&gt;kubectl get events&lt;/code&gt; 命令查看是否加载出了问题。&lt;/p&gt;

&lt;p&gt;Pod 变为 Running 状态之后，就可以尝试采用上面 YAML 中定义的 Node Port 来访问 Jenkins 界面了，经过一段时间的 &lt;code&gt;&amp;quot;Jenkins正在启动，请稍后.....&amp;quot;&lt;/code&gt;，系统要求输入 &lt;code&gt;/data/jenkins/secrets/initialAdminPassword&lt;/code&gt; 文件中保存的初始密码，假设前面 get pods 命令得到的 Pod 名称是 &lt;code&gt;jenkins-7nmka&lt;/code&gt;，这里就可以使用 &lt;code&gt;kubectl exec jenkins-7nmka cat /data/jenkins/secrets/initialAdminPassword&lt;/code&gt; 来查看，获取结果后即可复制黏贴到录入框中继续安装。&lt;/p&gt;

&lt;p&gt;接下来的插件安装，为节省时间直接 Select None。接下来设置管理员用户名密码，保存后，安装结束。&lt;/p&gt;

&lt;h2 id=&#34;安装和配置-kubernetes-插件&#34;&gt;安装和配置 Kubernetes 插件&lt;/h2&gt;

&lt;h3 id=&#34;安装&#34;&gt;安装&lt;/h3&gt;

&lt;p&gt;接下来就是进入 &lt;code&gt;pluginManager/available&lt;/code&gt; 页面安装插件。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;如果网络需要代理，可以进入网址 &lt;code&gt;/pluginManager/advanced&lt;/code&gt;，设置代理服务器。
如果可选插件页面为空，可以在 &lt;code&gt;/pluginManager/advanced&lt;/code&gt; 页面里面点击 &amp;ldquo;立即获取&amp;rdquo; 按钮进行刷新。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;过滤框中输入 &amp;ldquo;kubernetes&amp;rdquo; 会看到列表中的 &amp;ldquo;Kubernetes plugin&amp;rdquo;，选择安装。&lt;/p&gt;

&lt;h3 id=&#34;配置-jenkins&#34;&gt;配置 Jenkins&lt;/h3&gt;

&lt;p&gt;安装成功后，进入配置页面（&lt;code&gt;/configure&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;首先为了测试方便，我们把 “执行者数量” 设置为 0，也就是说只使用 Jenkins Slave 进行构建。&lt;/p&gt;

&lt;h3 id=&#34;配置-kubernetes&#34;&gt;配置 Kubernetes&lt;/h3&gt;

&lt;p&gt;可以看到，页面下方有一个按钮 “新增一个云”，点击后出现 Kubernetes 配置项目。&lt;/p&gt;

&lt;p&gt;这里我们用无认证的 http 方式进行连接，Kubernetes URL 中填入 API Server 的 http 地址，例如：&lt;code&gt;http://10.211.55.5:8080&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;Jenkins URL 中，这里要注意我们不应该使用浏览器地址栏中的 Node 地址，而是应该使用集群内部的服务地址，根据上文中 Service 的定义，这里使用 &lt;code&gt;http://jenkins:8080&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;最后是 ”Add Pod Template“ 按钮，来定义 Slave 的 Pod 模板：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;这里的 &amp;ldquo;Docker Image&amp;rdquo; 项目跟上面使用的是同一个地址，也就是 &lt;code&gt;10.211.55.5:5000/jenkins:2.7.4.5&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;根据前文说道的环境变量，这里我们新增一个环境变量：JENKINS_MODE=SLAVE，&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Jenkins slave root directory&amp;rdquo; 这一项填写 &lt;code&gt;/data/jenkins&lt;/code&gt;，&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Command to run slave agent   &amp;rdquo; 这里填写镜像中的启动命令 &lt;code&gt;/usr/local/bin/run.sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;填写完成后，保存。&lt;/p&gt;

&lt;h3 id=&#34;配置端口&#34;&gt;配置端口&lt;/h3&gt;

&lt;p&gt;最后需要到安全配置页面（&lt;code&gt;/configureSecurity&lt;/code&gt;），&lt;code&gt;TCP port for JNLP agents&lt;/code&gt; 一项填写固定端口 8081。&lt;/p&gt;

&lt;h2 id=&#34;hello-world&#34;&gt;Hello world&lt;/h2&gt;

&lt;h3 id=&#34;创建&#34;&gt;创建&lt;/h3&gt;

&lt;p&gt;新建一个 Free Style 项目。构建步骤中新增 &amp;ldquo;Execute shell script&amp;rdquo; 环节，并保存，内容为&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;echo &amp;quot;Hello World&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;构建&#34;&gt;构建&lt;/h3&gt;

&lt;p&gt;点击该任务的 “立即构建” 按钮，即可触发构建动作。&lt;/p&gt;

&lt;p&gt;因为前面我们设置 Master 不执行构建工作，所以在构建启动之后，会在构建执行状态中看到有节点被动态新建，来执行我们的构建过程。这一过程中如果使用 &lt;code&gt;kubectl get events&lt;/code&gt; 或者 &lt;code&gt;kubectl get pods&lt;/code&gt; 命令，能看到 Jenkins Slave Pod 的创建、执行和销毁过程。&lt;/p&gt;

&lt;h2 id=&#34;尾声&#34;&gt;尾声&lt;/h2&gt;

&lt;p&gt;上面只是一个最为基础的构建过程，甚至都无法称为是一个完整过程，Kubernetes 插件还提供了很多其他选项，结合自定义的 Slave 镜像，能够完成更多更复杂的任务，用于配合实际的生产过程。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 集群资源的那些事</title>
      <link>/post/resource-in-kubernetes/</link>
      <pubDate>Wed, 08 Mar 2017 05:38:56 +0800</pubDate>
      <guid>/post/resource-in-kubernetes/</guid>
      <description>

&lt;p&gt;大多数时候，我们在跟 K8S 玩耍的时候，主要目的就是：“把 XXX 打个镜像，在集群上跑起来 ——— 诶快看，真的跑起来了嘿！”。&lt;/p&gt;

&lt;p&gt;Kubernetes 和 Docker 的缺省配置，就能够帮我们省却很多麻烦。不过大家都很清楚，资源问题是无法回避的，我们在传统 IT 环境下遇到的各种资源相关问题，在容器集群环境下一样要得到解决，多租户动态分配环境下，这些问题会更加复杂。&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;h3 id=&#34;volume&#34;&gt;Volume&lt;/h3&gt;

&lt;p&gt;一般我们会用存储卷的方式来给 Pod 提供存储资源。&lt;/p&gt;

&lt;p&gt;起初的存储卷，在用量的控制方面，只能借存储的实际提供者的能力来进行。例如可以限制 GlusterFS 中 Volume 的大小。&lt;/p&gt;

&lt;p&gt;接下来出现了 Persistent Volume (PV) 和  Persistent Volume Claim (PVC) 这一组对象，完成了 “生产——消费” 关系，这就可以通过 Provision -&amp;gt; Claim 的方式，来对存储资源进行控制。&lt;/p&gt;

&lt;p&gt;而最新版本中还出现了动态卷供给的功能，能够对这一部分功能进行简化，无需首先建立 PV，直接建立 PVC 即可。&lt;/p&gt;

&lt;p&gt;有了 PVC 这一能力之后，Kubernetes 就借用这一对象对 Namespace 的存储访问进行了限制：&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;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;requests.storage&lt;/td&gt;
&lt;td&gt;所有的 PVC 申请容量之和不能超过此数值&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;persistentvolumeclaims&lt;/td&gt;
&lt;td&gt;一个 Namespace 中 PVC 的总数（Count）&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;storage-class-name&amp;gt;.storageclass.storage.k8s.io/requests.storage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;所有针对该 StorageClass 的 PVC 所申请的存储总容量不得超出这一数值&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;storage-class-name&amp;gt;.storageclass.storage.k8s.io/persistentvolumeclaims&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;所有针对该 StorageClass 最多能创建的 PVC 数量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&#34;日志&#34;&gt;日志&lt;/h3&gt;

&lt;p&gt;目前我们在实际使用中，爆磁盘的原因，除了对存储卷的控制不够之外，还有一个重要的点就是容器的日志，缺省情况下 Docker 使用的日志驱动是 json-file，这一驱动有个附加参数 &lt;code&gt;--log-opt max-size=[size]&lt;/code&gt; 可以用来限制日志的最大占用空间。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/engine/admin/logging/overview/&#34; target=&#34;_blank&#34;&gt;Docker 官方文档&lt;/a&gt; 还提供了很多其他的日志选项供选择。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;node&#34;&gt;Node&lt;/h3&gt;

&lt;p&gt;除了上面讲到的集群层面的问题之外，磁盘空间还对 Node(Kubelet) 的健康有重大影响。Kubelet 有几个参数用于对存储使用进行控制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--low-diskspace-threshold-mb&lt;/code&gt;：如果剩余空间低于这一限制，则拒绝在这一 Node 上新建 Pod（目前建议用新的驱逐规则来代替这一参数）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--image-gc-high-threshold&lt;/code&gt;：高于该值则启动 GC。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--image-gc-low-threshold&lt;/code&gt;：低于该值拒绝启动 GC。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在驱逐策略中，提供了如下几个磁盘相关的参数：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nodefs.available&lt;/li&gt;
&lt;li&gt;nodefs.inodesFree&lt;/li&gt;
&lt;li&gt;imagefs.available&lt;/li&gt;
&lt;li&gt;imagefs.inodesFree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里把 Node 磁盘分为 node 和 image 两种分别度量其 available 和 inodes，应该说比上面的 threshold 更加精确了&lt;/p&gt;

&lt;h2 id=&#34;cpu-和内存&#34;&gt;CPU 和内存&lt;/h2&gt;

&lt;p&gt;这一对资源应该算是 Kubernetes 中的 “经典” 资源了。Kubernetes 对 CPU 和内存提供了 requests/limits 两种度量，可以在 Container 的 Spec 中进行指定。&lt;/p&gt;

&lt;p&gt;在 namespace 一级中，提供了如下的总量限制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;limits.cpu：所有非结束状态的 Pod 的 CPU limit 总数。&lt;/li&gt;
&lt;li&gt;limits.memory：所有非结束状态的 Pod 的 内存 limit 总数。&lt;/li&gt;
&lt;li&gt;requests.cpu：所有非结束状态的 Pod 的 CPU request 总数。&lt;/li&gt;
&lt;li&gt;requests.memory：所有非结束状态的 Pod 的 CPU request 总数。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;node-1&#34;&gt;Node&lt;/h3&gt;

&lt;p&gt;和前面的磁盘的情况类似，Kubelet 中对 CPU 和内存也有新旧两套切换中的体系来进行限制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--kube-reserved&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;驱逐策略中提供了如下参数：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memory.available&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;quota-和-limitrange&#34;&gt;quota 和 limitrange&lt;/h3&gt;

&lt;p&gt;这是两个不同的 API Object，分别对应 namespace 的配额，和运行应用(Pod/Container)的资源限制。&lt;/p&gt;

&lt;h2 id=&#34;gpu&#34;&gt;GPU&lt;/h2&gt;

&lt;p&gt;这方面基本没有接触，但是随着深度学习之类名词的迅速炒热，相信 Kubernetes 会快速跟进的。&lt;/p&gt;

&lt;p&gt;将在 1.6 中推出多 GPU 支持的 Alpha 版本。&lt;/p&gt;

&lt;h2 id=&#34;网络&#34;&gt;网络&lt;/h2&gt;

&lt;p&gt;在 1.5 中，网络策略已经成为 Beta 版本，利用这一对象，横向可以实现 namespace 之间的隔离；纵向可以定义 namespace 内不同职责应用的网络访问能力。这就有效的阻断了不同租户之间利用 dns 进行授权之外的访问的途径。&lt;/p&gt;

&lt;h2 id=&#34;参考资料&#34;&gt;参考资料：&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;网络策略：&lt;code&gt;https://kubernetes.io/docs/user-guide/networkpolicies/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;驱逐策略：&lt;code&gt;http://blog.fleeto.us/translation/configuring-out-resource-handling&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;存储：

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http://blog.fleeto.us/translation/dynamic-provisioning-and-storage-classes-kubernetes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://blog.fleeto.us/translation/persistent-volumes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Linkerd &#43; Namerd，实现 Kubernetes 集群的灰度发布</title>
      <link>/post/linerd-on-kubernetes/</link>
      <pubDate>Tue, 14 Feb 2017 06:42:02 +0800</pubDate>
      <guid>/post/linerd-on-kubernetes/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;主要内容源于 &lt;code&gt;https://blog.buoyant.io/2016/11/04/a-service-mesh-for-kubernetes-part-iv-continuous-deployment-via-traffic-shifting/&lt;/code&gt; ，砍掉了 Jenkins 等附加部分，更换了更加易于理解的示例应用，以保证主干突出。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes 所提供的 rolling-update 功能提供了一种渐进式的更新过程，然而其滚动过程并不容易控制，对于灰度发布的需要来说，仍稍显不足，这里介绍一种利用 Linkerd 方案进行流量切换的思路。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;官网介绍：linker∙d is a transparent proxy that adds service discovery, routing, failure handling, and visibility to modern software applications。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;本文从实际操作入手，上线两个版本的简单应用，利用这一组合完成流量的切换和测试过程。&lt;/p&gt;

&lt;h2 id=&#34;测试目标&#34;&gt;测试目标&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;同时上线两个版本的应用。两个应用均可工作，利用不同输出进行区分。&lt;/li&gt;
&lt;li&gt;动态调整分配给两个版本的流量。&lt;/li&gt;
&lt;li&gt;利用 CURL 进行流量分配的测试。&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;这里利用一个 1.2 以上版本的 Kubernetes 集群进行演示：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Server / Registry：10.211.55.62&lt;/li&gt;
&lt;li&gt;Node：10.211.66.63&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外因某些原因，需要有能力获取 Dockerhub 的镜像。&lt;/p&gt;

&lt;p&gt;例子程序很简单，用一个 PHP 文件显示环境变量中的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
echo getenv(&amp;quot;VAR_LABEL&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Docker file 继承自 &lt;code&gt;dustise/lamp:latest&lt;/code&gt;，文件内容如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM dustise/lamp
COPY index.php /web/codebase
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;利用 Docker build 创建镜像，这里命名为 lamp:gray，备用。&lt;/p&gt;

&lt;h2 id=&#34;创建工作负载&#34;&gt;创建工作负载&lt;/h2&gt;

&lt;p&gt;做一个简单的 yaml 文件来加载蓝绿两组应用，名字、环境变量和端口三个位置需要更改：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
kind: ReplicationController
apiVersion: v1
metadata:
  name: green
# 此处省略若干
        env:
        - name: VAR_LABEL
          value: &#39;green&#39;
---
kind: Service
apiVersion: v1

# 此处省略若干

  type: NodePort
  ports:
  - protocol: TCP
    nodePort: 32001
    port: 80
    targetPort: 80
    name: http
  selector:
    name: green
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;利用 &lt;code&gt;kubectl create -f green.yaml&lt;/code&gt; （ 以及 blue.yaml ）之后，可以利用 curl 或者浏览器检查运行情况，如果正常，两个端口的访问应该分别会返回 &lt;code&gt;green&lt;/code&gt; 和 &lt;code&gt;blue&lt;/code&gt; ，这里的端口命名很重要，这一名称会被后面的规则引用到。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意，这里 NodePort 并非必须，仅为测试方便。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;运行-namerd&#34;&gt;运行 Namerd&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;此处 yaml 主要来自于官网 &lt;code&gt;https://raw.githubusercontent.com/BuoyantIO/linkerd-examples/master/k8s-daemonset/k8s/namerd.yml&lt;/code&gt;
为适应本地环境，将原有 Loadbalancer 类型的服务改为 NodePort&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;略微做一下讲解。&lt;/p&gt;

&lt;p&gt;整个 yaml 由四部分组成：&lt;/p&gt;

&lt;h3 id=&#34;thirdpartyresource&#34;&gt;ThirdPartyResource&lt;/h3&gt;

&lt;p&gt;这部分被用于做 Namerd 的存储后端。&lt;/p&gt;

&lt;h3 id=&#34;configmap&#34;&gt;Configmap&lt;/h3&gt;

&lt;p&gt;作为 Namerd 的配置，其中定义了这样几个内容（详情可参见 &lt;code&gt;https://linkerd.io/config/0.8.5/namerd/index.html#introduction&lt;/code&gt;）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;管理端口 9990&lt;/li&gt;
&lt;li&gt;storage：存储定义，通过 8001 端口同 Kube Api Server 通信，完成在 ThrdPartyResource 中的访问（8001 端口由 kubectl proxy 指令开通）&lt;/li&gt;
&lt;li&gt;namer：定义服务发现能力由 Kubernetes 提供。&lt;/li&gt;
&lt;li&gt;interface 部分则是定义了两种支持协议。其中 HTTP Controller 可以接收 namerctl 的控制指令。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;rc&#34;&gt;RC&lt;/h3&gt;

&lt;p&gt;这部分不新鲜，除了 namerd 之外，还利用 kubectl proxy 提供通信端口给 namerd，颇有蛇足之嫌。正确的打开方式应该是直接和  Kube API Server 进行通信。&lt;/p&gt;

&lt;h3 id=&#34;service&#34;&gt;Service&lt;/h3&gt;

&lt;p&gt;这里注意服务类型的变更（ LoadBalancer -&amp;gt; NodePort ），需要暴露 4180 和 9990 两个端口，分别作为控制端口和界面端口。&lt;/p&gt;

&lt;p&gt;利用 kubectl 启用之后，就可以在指定的端口查看管理界面了。此时的管理界面没有做任何配置，因此比较单薄。&lt;/p&gt;

&lt;h3 id=&#34;添加规则&#34;&gt;添加规则&lt;/h3&gt;

&lt;p&gt;下面来安装 namerd 的控制工具，namerctl&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;go get -u github.com/buoyantio/namerctl
go install github.com/buoyantio/namerctl
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来创建一条规则：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/host=&amp;gt;/#/io.l5d.k8s/default/http;
/http/*/*/*=&amp;gt;8*/host/blue&amp;amp;2*/host/green;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段代码表示该服务同时连接 blue 和 green 两个后端服务，按照 &lt;sup&gt;80&lt;/sup&gt;&amp;frasl;&lt;sub&gt;20&lt;/sub&gt; 的比例进行流量分配。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;namerctl dtab create [file name] --base-url&lt;/code&gt;，这里 base-url 取值就是我们给 namerd 设置的 Nodeport。&lt;/p&gt;

&lt;p&gt;接下来就能够看到管理界面上显示出新的规则了。&lt;/p&gt;

&lt;h2 id=&#34;运行-linkerd&#34;&gt;运行 Linkerd&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;这里同样基于官方的 &lt;code&gt;https://raw.githubusercontent.com/BuoyantIO/linkerd-examples/master/k8s-daemonset/k8s/linkerd-namerd.yml&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;需要注意的是，官方给出的 yaml 文件中有一处 bug，使得这个 yaml 只能在缺省的 namespace 和 domain suffix 下运行。需要纠正对 namerd 的访问方式，删除 Namerd 后面的 &lt;code&gt;default.svc.cloud.local&lt;/code&gt; 即可。&lt;/p&gt;

&lt;p&gt;同样的，他的服务端口和管理端口都应该改用 NodePort 方式进行暴露。&lt;/p&gt;

&lt;p&gt;运行后，同样可以看到 Linkerd 的管理界面。&lt;/p&gt;

&lt;h2 id=&#34;测试&#34;&gt;测试&lt;/h2&gt;

&lt;p&gt;下面可以做一个简单的测试，来证明流量分配的有效性：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;for ((i=1;i&amp;lt;=300;i++)); do curl -s &amp;quot;http://10.211.55.63:30001/&amp;quot;;echo &amp;quot;&amp;quot;; done | grep -i blue| wc -l
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，随着循环次数的增加，其结果越来越趋近于 &lt;sup&gt;80&lt;/sup&gt;&amp;frasl;&lt;sub&gt;20&lt;/sub&gt; 的分配比例。&lt;/p&gt;

&lt;p&gt;接下来，我们修改上面的 dtab 为如下内容：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/host=&amp;gt;/#/io.l5d.k8s/default/http;
/http/*/*/*=&amp;gt;8*/host/blue&amp;amp;8*/host/green;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;重新进行测试，就可以看到，流量分配已经发生了变化。另外，还可以在 Linkerd 的管理界面上看到网络流量的变化情况。&lt;/p&gt;

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

&lt;p&gt;这一组合基本能够满足流量渐变分配的功能需求，同时也有如豆瓣这样的大厂使用，但他的 dtab 还是个相对复杂的东西，如果在生产上进行使用，还是需要进一步的学习。&lt;/p&gt;

&lt;p&gt;另外，按照其文档中所陈述的功能范围内容来看，仅用来做流量分配还是颇有点大材小用的味道，从个人来说，我倾向于一些更轻量级的解决方法。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 支持 OpenAPI</title>
      <link>/post/openapi-support-in-kubernetes/</link>
      <pubDate>Sun, 25 Dec 2016 04:22:42 +0800</pubDate>
      <guid>/post/openapi-support-in-kubernetes/</guid>
      <description>&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/12/kubernetes-supports-openapi&#34; target=&#34;_blank&#34;&gt;Kubernetes supports OpenAPI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.openapis.org/&#34; target=&#34;_blank&#34;&gt;Open API&lt;/a&gt; 让 API 提供者可以定义自己的操作和模型，并让开发者可以自动化的生成喜欢语言的客户端，用以和 API 服务器通信。Kubernetes 已经支持 Swagger 1.2（OpenAPI 规范的前身）有一段时间了，但是这一标准不够完整和有效，凭借这一支持，非常难生成工具或客户端。&lt;/p&gt;

&lt;p&gt;在 Kubernetes 1.4，我们对目前的模型和操作进行了升级，引入了 Open API 规范（在没被捐献给 &lt;a href=&#34;https://www.openapis.org/about&#34; target=&#34;_blank&#34;&gt;Open API&lt;/a&gt; 之前被称作 Swagger 2.0）支持的 Alpha 版本。从 &lt;a href=&#34;http://blog.kubernetes.io/2016/12/kubernetes-1.5-supporting-production-workloads.html&#34; target=&#34;_blank&#34;&gt;Kubernetes 1.5&lt;/a&gt; 开始，OpenAPI 规范的支持已经完备，能够直接从 Kubernetes 源码生成规范，对于模型和方法的任何变更，都会保障文档和规范的完全同步。&lt;/p&gt;

&lt;p&gt;新规范让我们有了更好的 API 文档，甚至还有了一个 &lt;a href=&#34;https://github.com/kubernetes-incubator/client-python&#34; target=&#34;_blank&#34;&gt;Python 客户端&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;这一模块化的规范用 GroupVersion 进行分隔，这一做法属于未雨绸缪，我们想要让不同的 API Server 使用不同的 GroupVersion。&lt;/p&gt;

&lt;p&gt;规范的结构在 &lt;a href=&#34;https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md&#34; target=&#34;_blank&#34;&gt;Open API spec definition&lt;/a&gt; 中有解释。我们用 &lt;a href=&#34;https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#tag-object&#34; target=&#34;_blank&#34;&gt;operation 标记&lt;/a&gt; 来拆分每个 GroupVersion 并尽可能的丰富其中的模型、路径、操作等信息。操作的参数、调用方法以及响应都有文档描述。&lt;/p&gt;

&lt;p&gt;例如，获取 Pod 信息的 OpenAPI 规范&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
...
  &amp;quot;paths&amp;quot;: {
&amp;quot;/api/v1/namespaces/{namespace}/pods/{name}&amp;quot;: {
    &amp;quot;get&amp;quot;: {
     &amp;quot;description&amp;quot;: &amp;quot;read the specified Pod&amp;quot;,
     &amp;quot;consumes&amp;quot;: [
      &amp;quot;*/*&amp;quot;
     ],
     &amp;quot;produces&amp;quot;: [
      &amp;quot;application/json&amp;quot;,
      &amp;quot;application/yaml&amp;quot;,
      &amp;quot;application/vnd.kubernetes.protobuf&amp;quot;
     ],
     &amp;quot;schemes&amp;quot;: [
      &amp;quot;https&amp;quot;
     ],
     &amp;quot;tags&amp;quot;: [
      &amp;quot;core_v1&amp;quot;
     ],
     &amp;quot;operationId&amp;quot;: &amp;quot;readCoreV1NamespacedPod&amp;quot;,
     &amp;quot;parameters&amp;quot;: [
      {
       &amp;quot;uniqueItems&amp;quot;: true,
       &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,
       &amp;quot;description&amp;quot;: &amp;quot;Should the export be exact.  Exact export maintains cluster-specific fields like &#39;Namespace&#39;.&amp;quot;,
       &amp;quot;name&amp;quot;: &amp;quot;exact&amp;quot;,
       &amp;quot;in&amp;quot;: &amp;quot;query&amp;quot;
      },
      {
       &amp;quot;uniqueItems&amp;quot;: true,
       &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,
       &amp;quot;description&amp;quot;: &amp;quot;Should this value be exported.  Export strips fields that a user can not specify.&amp;quot;,
       &amp;quot;name&amp;quot;: &amp;quot;export&amp;quot;,
       &amp;quot;in&amp;quot;: &amp;quot;query&amp;quot;
      }
     ],
     &amp;quot;responses&amp;quot;: {
      &amp;quot;200&amp;quot;: {
       &amp;quot;description&amp;quot;: &amp;quot;OK&amp;quot;,
       &amp;quot;schema&amp;quot;: {
        &amp;quot;$ref&amp;quot;: &amp;quot;#/definitions/v1.Pod&amp;quot;
       }
      },
      &amp;quot;401&amp;quot;: {
       &amp;quot;description&amp;quot;: &amp;quot;Unauthorized&amp;quot;
      }
     }
    },
…
}
…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有了这些信息，以及 &lt;code&gt;kube-apiserver&lt;/code&gt; 的 URL，就可以据此来调用接口了（&lt;code&gt;/api/v1/namespaces/{namespace}/pods/{name}&lt;/code&gt;），参数包括 &lt;code&gt;name&lt;/code&gt;、&lt;code&gt;exact&lt;/code&gt; 以及 &lt;code&gt;export&lt;/code&gt; 等，调用结果会返回 Pod 信息。客户库生成器也会使用这些信息来创建一个 API 函数调用来读取 Pod 信息。例如 &lt;a href=&#34;https://github.com/kubernetes-incubator/client-python&#34; target=&#34;_blank&#34;&gt;Python 客户端&lt;/a&gt; 能够很简单的进行如下调用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;from kubernetes import client
ret = client.CoreV1Api().read_namespaced_pod(name=&amp;quot;pods_name&amp;quot;, namespace=&amp;quot;default&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://gist.github.com/mbohlool/d5ec1dace27ef90cf742555c05480146&#34; target=&#34;_blank&#34;&gt;https://gist.github.com/mbohlool/d5ec1dace27ef90cf742555c05480146&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一个简化版的 &lt;a href=&#34;https://gist.github.com/mbohlool/d5ec1dace27ef90cf742555c05480146&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;read_namespaced_pod&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;Python Client：&lt;a href=&#34;https://github.com/kubernetes-incubator/client-python&#34; target=&#34;_blank&#34;&gt;https://github.com/kubernetes-incubator/client-python&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;还可以使用 Swagger-codegen 文档生成器来根据这些信息生成文档：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;GET /api/v1/namespaces/{namespace}/pods/{name}
(readCoreV1NamespacedPod)
read the specified Pod
Path parameters
name (required)
Path Parameter — name of the Pod
namespace (required)
Path Parameter — object name and auth scope, such as for teams and projects
Consumes
This API call consumes the following media types via the Content-Type request header:
*/*

Query parameters
pretty (optional)
Query Parameter — If &#39;true&#39;, then the output is pretty printed.
exact (optional)
Query Parameter — Should the export be exact. Exact export maintains cluster-specific fields like &#39;Namespace&#39;.
export (optional)
Query Parameter — Should this value be exported. Export strips fields that a user can not specify.
Return type
v1.Pod

Produces
This API call produces the following media types according to the Accept request header; the media type will be conveyed by the Content-Type response header.
application/json
application/yaml
application/vnd.kubernetes.protobuf
Responses
200
OK v1.Pod
401
Unauthorized
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有两种方式访问 OpenAPI ：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;从 &lt;code&gt;kube-apiserver&lt;/code&gt;/swagger.json。这个文件会包含所有启用的 GroupVersion 方法和模型，其中的内容会是该 API Server 所对应的最新版本。&lt;/li&gt;
&lt;li&gt;Kubernetes 的 Github 仓库，可以访问 master 或者其他指定的 Release。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有很多&lt;a href=&#34;http://swagger.io/tools/&#34; target=&#34;_blank&#34;&gt;工具&lt;/a&gt; 能和这些 API 协同工作，例如可以用 &lt;a href=&#34;http://swagger.io/swagger-editor/&#34; target=&#34;_blank&#34;&gt;swagger editor&lt;/a&gt; 来打开规范文件并渲染文档，或者生成客户端；还可以直接利用 &lt;a href=&#34;http://swagger.io/swagger-codegen/&#34; target=&#34;_blank&#34;&gt;swagger codegen&lt;/a&gt; 来生成文档和客户端。自动生成的客户端多数时候是开箱即用的。不过可能需要做一些登录和 Kubernetes 相关的设置。可以使用 &lt;a href=&#34;https://github.com/kubernetes-incubator/client-python&#34; target=&#34;_blank&#34;&gt;Python 客户端&lt;/a&gt; 作为模板来开发自己的客户端。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 的 Windows Server 支持</title>
      <link>/post/windows-server-support-in-kubernetes/</link>
      <pubDate>Thu, 22 Dec 2016 23:20:49 +0800</pubDate>
      <guid>/post/windows-server-support-in-kubernetes/</guid>
      <description>

&lt;p&gt;响应群众呼声，&lt;a href=&#34;http://blog.kubernetes.io/2016/12/kubernetes-1.5-supporting-production-workloads.html&#34; target=&#34;_blank&#34;&gt;Kubernetes 1.5&lt;/a&gt; 包含了对 Windows Servern 的支持。&lt;a href=&#34;http://www.gartner.com/document/3446217&#34; target=&#34;_blank&#34;&gt;80%&lt;/a&gt; 的企业应用运行于 Linux + Java 或 .Net + Windows 平台上。Kubernetes 正在 Preview 阶段的这一功能，是贴近企业需求的一次努力。&lt;/p&gt;

&lt;p&gt;Kubernetes Windows Server 2016 以及 Windows 容器支持包含了下列功能的预览版本：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;容器化的多平台应用&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用操作系统原生语言，例如 Go 和 .NET 核心开发的应用，在以前是不可能再 Linux 和 Windows  之间进行编排的。现在 Kubernetes 有了 windows Server 2016 支持，这些应用就能够同时部署在 Windows 和 Linux 之下了，开发者可以自行选择操作系统运行时。这一功能，消费者已经等了 20 年了。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;同时支持 Windows Server 容器和 Hyper-V 容器&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Windows Server 2016 有两种类型的容器。Windows 容器会共享内核，比较像 Linux 的 Docker 容器。Hyper-V 容器比虚拟机轻量，同时还提供了更好的隔离。他具有独立的内核，直接的内存访问。Kubernetes 对这两种容器都有编排能力。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;扩展应用生态&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为 Kubernetes 添加 Windows Server 支持的一个好处就是扩展了 Kuberntes 应用的生态环境：IIS，.NET，Windows 服务，ASP.NET，.NET 核心，以及一些其他类型的应用现在可以以 Windows Server 容器的形式被 Kubernetes 调度了。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;支持更多数据中心&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;包含全球 2000 以及财富 500 的很多企业采用了 Kubernetes，托管了几万个应用。加入 Windows 支持之后，这些组织就可以更近一步的将 Windows 应用纳入 Kubernetes 进行管理了。&lt;/p&gt;

&lt;p&gt;将 Windows Server 支持加入 Kubernetes 的过程由 &lt;a href=&#34;https://github.com/kubernetes/community/blob/master/sig-windows/README.md&#34; target=&#34;_blank&#34;&gt;Windows Special Interest Group(SIG)&lt;/a&gt; 牵头完成，其中包含了 Apprenda、Google、Red Hat 以及微软等众多厂商。微软的首席项目经理  Taylor Brown 表示：社区工作进一步推动了 Kubernetes 的 windows 容器支持，落实了微软对 Windows 和 Linux 两个生态系统的支持承诺。&lt;/p&gt;

&lt;h2 id=&#34;使用指南&#34;&gt;使用指南&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;在哪里使用？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;现在就可以开始进行测试，并提供反馈。未来的几个月里，这一功能将会进入 GA 状态，可以在这段时间差进行生产环境的设置。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;能怎么用？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;多数的 Kuberntes 对象，比如 Pods，Services，Labels 都和 Windows Container 兼容。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;还有什么没完善？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;因为网络命名空间的问题导致的 Pod 抽象差异。同一个 Pod 内的 Windows 容器无法利用 localhost 进行通信。Linux 容器就可以利用网络命名空间来共享网络栈。
- DNS 能力实现不完整
- 容器内不支持 UDP&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;什么时候可用？&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;需要再等几个发布周期。在 GA 之前，要理顺网络和一些其他的事情，包含 Windows Nano 和 Windows Server Core 的支持。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;技术演示&#34;&gt;技术演示&lt;/h2&gt;

&lt;h2 id=&#34;roadmap&#34;&gt;Roadmap&lt;/h2&gt;

&lt;p&gt;在 Kuberntes 1.5 中，Windows Server 容器支持正处于 Alpha 阶段，当然并不会止步于此。客户希望获得企业级的稳定的容器调度和管理功能，来支撑整个技术路线。这要求我们对 Windows 和 Linux 一视同仁。Windows Server SIG 将在未来的发布中做出以下增强：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;网络 SIG 会持续同微软的合作来增强 Windows Server 容器的网络核心，尤其是容器模式的网络，以及本地叠加网络的支持。&lt;/li&gt;
&lt;li&gt;OOBE 增强 Windows Server Node 的设置部署和检测功能，包括在任何云上部署的能力。&lt;/li&gt;
&lt;li&gt;运行时操作 定义 CRI 监控的过程中，SIG 将在其中扮演重要角色，将为 Windows Server 容器提供深入的监控能力。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;起步&#34;&gt;起步&lt;/h2&gt;

&lt;p&gt;请查看 &lt;a href=&#34;http://kubernetes.io/docs/getting-started-guides/windows/&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt; 获取更多细节信息，以便尝试 Windows Server 2016 上的 Kubernetes。如果读者愿意帮助促进 Windows Server 支持，请联系 &lt;a href=&#34;https://github.com/kubernetes/community/blob/master/sig-windows/README.md&#34; target=&#34;_blank&#34;&gt;Windows Server SIG&lt;/a&gt;，或者联系 SIG Lead &lt;a href=&#34;https://github.com/michmike&#34; target=&#34;_blank&#34;&gt;Michael Michael&lt;/a&gt;。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>StatefulSet: Kubernetes 中对有状态应用的运行和伸缩</title>
      <link>/post/statefulset-in-kubernetes/</link>
      <pubDate>Thu, 22 Dec 2016 03:43:00 +0800</pubDate>
      <guid>/post/statefulset-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/12/statefulset-run-scale-stateful-applications-in-kubernetes&#34; target=&#34;_blank&#34;&gt;StatefulSet: Run and Scale Stateful Applications Easily in Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在最新发布的 &lt;a href=&#34;http://blog.kubernetes.io/2016/12/kubernetes-1.5-supporting-production-workloads.html&#34; target=&#34;_blank&#34;&gt;Kubernetes 1.5&lt;/a&gt; 我们将过去的 PetSet 功能升级到了 Beta 版本，并重新命名为 &lt;a href=&#34;http://kubernetes.io/docs/concepts/abstractions/controllers/statefulsets/&#34; target=&#34;_blank&#34;&gt;StatefulSet&lt;/a&gt;。除了依照社区民意改了名字之外，这一 API 对象并没有太大变化，不过我们在向集合里部署 Pod 的过程中加入了“每索引最多一个”的语义。有了顺序部署、顺序终结、唯一网络名称以及持久稳定的存储，我们认为，对于大量的有状态容器化负载，我们已经具备了一定的支持能力。我们并不是宣称这一功能已经完全完成，但是我们相信他已经处于一个可用状态，并且我们会在推动其正式发布的过程中保持其兼容性。&lt;/p&gt;

&lt;h2 id=&#34;statefulset-的采用时机&#34;&gt;StatefulSet 的采用时机&lt;/h2&gt;

&lt;p&gt;在 Kubernetes 中，&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/user-guide/replicasets/&#34; target=&#34;_blank&#34;&gt;ReplicaSets&lt;/a&gt; 都是运行无状态应用的有效手段。但这两种方式对于有状态应用来说就不太合适了。StatefulSet 的目的就是给为数众多的有状态负载提供正确的控制器支持。然而需要注意的是，不一定所有的有存储应用都是适合移植到 Kubernetes 上的，在移植存储层和编排框架之前，需要回答以下几个问题。&lt;/p&gt;

&lt;h3 id=&#34;应用是否可以使用远程存储&#34;&gt;应用是否可以使用远程存储？&lt;/h3&gt;

&lt;p&gt;目前，我们推荐用远程存储来使用 StatefulSets，就要对因为网络造成的存储性能损失有一个准备：即使是专门优化的实例，也无法同本地加载的 SSD 相提并论。你的云中的网络存储，能够满足 SLA 要求么？如果答案是肯定的，那么利用 StatefulSet 运行这些应用，就能够获得自动化的优势。如果应用所在的 Node 发生故障，包含应用的 Pod 会调度到其他 Node 上，在这之后会重新加载他的网络存储以及其中的数据。&lt;/p&gt;

&lt;h3 id=&#34;这些应用是否有伸缩需求&#34;&gt;这些应用是否有伸缩需求？&lt;/h3&gt;

&lt;p&gt;用 StatefulSet 运行应用会带来什么好处呢？你的整个组织是否只需要一个应用实例？对该应用的伸缩是否会引起问题？如果你只需要较少的应用实例数量，这些实例能够满足组织现有的需要，而且可以预见的是，应用的负载不会很快增长，那么你的本地应用可能无需移植。&lt;/p&gt;

&lt;p&gt;然而，如果你的系统是微服务所构成的生态系统，就会比较频繁的交付新服务，如果更近一步，服务是有状态的，那么 Kubernetes 的自动化和健壮性特性会对你的系统有很大帮助。如果你已经在使用 Kubernetes 来管理你的无状态服务，你可能会想要在同一个体系中管理你的有状态应用。&lt;/p&gt;

&lt;h3 id=&#34;预期性能增长的重要性&#34;&gt;预期性能增长的重要性？&lt;/h3&gt;

&lt;p&gt;Kubernetes 还不支持网络或存储在 Pod 之间的隔离。如果你的应用不巧和嘈杂的邻居共享同一个节点，会导致你的 QPS 下降。解决方式是把 Pod 调度为该 Node 的唯一租户（独占服务器），或者使用互斥规则来隔离会争用网络和磁盘的 Pod，但是这就意味着用户必须鉴别和处置（竞争）热点。&lt;/p&gt;

&lt;p&gt;如果榨干有状态应用的最大 QPS 不是你的首要目标，而且你愿意也有能力处理竞争问题，似的有状态应用能够达到 SLA 需要，又如果对服务的移植、伸缩和重新调度是你的主要需求，Kubernetes 和 StatefulSet 可能就是解决问题的好方案了。&lt;/p&gt;

&lt;h3 id=&#34;你的应用是否需要特定的硬件或者实例类型&#34;&gt;你的应用是否需要特定的硬件或者实例类型&lt;/h3&gt;

&lt;p&gt;如果你的有状态应用在高端硬件或高规格实例上运行，而其他应用在通用硬件或者低规格实例上运行，你可能不想部署一个异构的集群。如果可以把所有应用都部署到统一实例规格的实例上，那么你就能够从 Kubernetes 获得动态资源调度和健壮性的好处。&lt;/p&gt;

&lt;h2 id=&#34;实践环节-zookeeper&#34;&gt;实践环节 - ZooKeeper&lt;/h2&gt;

&lt;p&gt;有两个原因让 [ZooKeeper] 成为 StatefulSet 的好例子。首先，StatefulSet 在其中演示了运行分布式、强一致性存储的应用的能力；其次，ZooKeeper 也是 &lt;a href=&#34;http://hadoop.apache.org/&#34; target=&#34;_blank&#34;&gt;Apache Hadoop&lt;/a&gt; 和 &lt;a href=&#34;https://kafka.apache.org/&#34; target=&#34;_blank&#34;&gt;Apache Kafka&lt;/a&gt; 在 Kubernetes 上运行的前置条件。在 Kubernetes 文档中有一个 &lt;a href=&#34;http://kubernetes.io/docs/tutorials/stateful-application/zookeeper/&#34; target=&#34;_blank&#34;&gt;深度教程&lt;/a&gt; 说明了在 Kubernetes 集群上部署 ZooKeeper Ensemble 的过程，这里会简要描述一下其中的关键特性。&lt;/p&gt;

&lt;h3 id=&#34;创建-zookeeper-的-ensemble&#34;&gt;创建 ZooKeeper 的 Ensemble&lt;/h3&gt;

&lt;p&gt;创建 Ensemble 很容易，只要用 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/kubectl/kubectl_create/&#34; target=&#34;_blank&#34;&gt;kubectl create&lt;/a&gt; 来根据定义来创建对象就可以了。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl create -f https://raw.githubusercontent.com/kubernetes/kubernetes.github.io/master/docs/tutorials/stateful-application/zookeeper.yaml
service &amp;quot;zk-headless&amp;quot; created
configmap &amp;quot;zk-config&amp;quot; created
poddisruptionbudget &amp;quot;zk-budget&amp;quot; created
statefulset &amp;quot;zk&amp;quot; created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来 StatefulSet 控制器开始&lt;strong&gt;顺序&lt;/strong&gt;创建各个 Pod，在创建后续 Pod 之前，首先要等前面的 Pod 运行成功并进入到就绪状态。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl get -w -l app=zk
NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Pending   0          0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       Pending   0         7s
zk-0      0/1       ContainerCreating   0         7s
zk-0      0/1       Running   0         38s
zk-0      1/1       Running   0         58s
zk-1      0/1       Pending   0         1s
zk-1      0/1       Pending   0         1s
zk-1      0/1       ContainerCreating   0         1s
zk-1      0/1       Running   0         33s
zk-1      1/1       Running   0         51s
zk-2      0/1       Pending   0         0s
zk-2      0/1       Pending   0         0s
zk-2      0/1       ContainerCreating   0         0s
zk-2      0/1       Running   0         25s
zk-2      1/1       Running   0         40s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查一下 StatefulSet 中每个 Pod 的主机名称，你会发现 Pod 的主机名也包含了 Pod 的顺序：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ for i in 0 1 2; do kubectl exec zk-$i -- hostname; done
zk-0
zk-1
zk-2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ZooKeeper 在一个名为 &amp;ldquo;myid&amp;rdquo; 的文件中保存了每个服务器的唯一标识符。这个标识符只是自然数。在 Ensemble 的服务器中，&amp;rdquo;myid&amp;rdquo; 文件中保存的数字就是 Pod 主机名中的顺序号加一。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ for i in 0 1 2; do echo &amp;quot;myid zk-$i&amp;quot;;kubectl exec zk-$i -- cat /var/lib/zookeeper/data/myid; done
myid zk-0
1
myid zk-1
2
myid zk-2
3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;基于主机名，每个 Pod 都有独立的网络地址，这个网域由 zk-headless 这一 Headless 服务所控制。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$  for i in 0 1 2; do kubectl exec zk-$i -- hostname -f; done
zk-0.zk-headless.default.svc.cluster.local
zk-1.zk-headless.default.svc.cluster.local
zk-2.zk-headless.default.svc.cluster.local
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 具有了唯一的序号和网络地址，就可以用来在 ZooKeeper 的配置文件中设置 Ensemble 成员了。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg
clientPort=2181
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/log
tickTime=2000
initLimit=10
syncLimit=2000
maxClientCnxns=60
minSessionTimeout= 4000
maxSessionTimeout= 40000
autopurge.snapRetainCount=3
autopurge.purgeInteval=1
server.1=zk-0.zk-headless.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-headless.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-headless.default.svc.cluster.local:2888:3888
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;StatefulSet 让用户可以用稳定、可重复的方式来部署 ZooKeeper。不会创建具有重复 ID 的服务器，服务器之间可以通过稳定的网络地址互相通信，因为 Ensemble 具有稳定的成员关系，因此 Leader 选拔和写入复制能力也得到了保障。&lt;/p&gt;

&lt;p&gt;检查 Ensemble 工作状况的最简单方式就是向一台服务器写入一个值，然后从另一台服务器中读取。可以利用 ZooKeeper 自带的 &amp;ldquo;zkCli.sh&amp;rdquo; 脚本来创建包含数据的 ZNode。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$  kubectl exec zk-0 zkCli.sh create /hello world
...

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
Created /hello
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用同一脚本，可以从 Ensemble 另外一台服务器中读取数据。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$  kubectl exec zk-1 zkCli.sh get /hello
...

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
world
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以用删除 zk StatefulSet 的方式停掉 Ensemble。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$  kubectl delete statefulset zk
statefulset &amp;quot;zk&amp;quot; deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;级联删除会销毁 StatefulSet 中的每个 Pod，并且按照创建顺序的反序来执行，只有在成功终结后面一个之后，才会继续下一个删除操作。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$  kubectl get pods -w -l app=zk
NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   0          14m
zk-1      1/1       Running   0          13m
zk-2      1/1       Running   0          12m
NAME      READY     STATUS        RESTARTS   AGE
zk-2      1/1       Terminating   0          12m
zk-1      1/1       Terminating   0         13m
zk-0      1/1       Terminating   0         14m
zk-2      0/1       Terminating   0         13m
zk-2      0/1       Terminating   0         13m
zk-2      0/1       Terminating   0         13m
zk-1      0/1       Terminating   0         14m
zk-1      0/1       Terminating   0         14m
zk-1      0/1       Terminating   0         14m
zk-0      0/1       Terminating   0         15m
zk-0      0/1       Terminating   0         15m
zk-0      0/1       Terminating   0         15m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以使用 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/kubectl/kubectl_apply/&#34; target=&#34;_blank&#34;&gt;kubectl apply&lt;/a&gt; 命令来重建 zk StatefulSet，并重新部署 Ensemble。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$  kubectl apply -f http://k8s.io/docs/tutorials/stateful-application/zookeeper.yaml
service &amp;quot;zk-headless&amp;quot; configured
configmap &amp;quot;zk-config&amp;quot; configured
statefulset &amp;quot;zk&amp;quot; created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果使用 &amp;ldquo;zkCli.sh&amp;rdquo; 脚本来尝试获取删除 StatefulSet 之前写入的数据，会发现数据依然存在。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$  kubectl exec zk-2 zkCli.sh get /hello
...

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
world
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所有的 Pod 都被销毁，他们一旦被重新调度，StatefulSet 也能保证 Ensemble 能够选拔新的 Leader 并继续提供服务。&lt;/p&gt;

&lt;h3 id=&#34;node-故障的容错&#34;&gt;Node 故障的容错&lt;/h3&gt;

&lt;p&gt;ZooKeeper 会在 Ensmble 的服务器中复制他的状态机，用于应对 Node 故障。缺省情况下 Kubernetes 调度器可以在同一个 Node 上部署属于 zk StatefulSet 的多个 Pod，假设 zk-0 和 zk-1 两个 Pod 被部署在同一个 Node 上，如果这一 Node 出现故障，ZooKeepers Ensemble 会因为数量不足造成无法提交写入，ZooKeeper 会出现服务中断，直到 Pod 被重新调度。&lt;/p&gt;

&lt;p&gt;在集群中，建议为关键进程预留更多资源，这样就能保证故障情况发生的时候能够迅速重新调度 Pod，缩短故障时间。&lt;/p&gt;

&lt;p&gt;如果这样无法 SLA 规定的停机时间，那么就应该使用 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/node-selection/&#34; target=&#34;_blank&#34;&gt;PodAntiAffinity( Pod 互斥性)&lt;/a&gt;注解。用来创建 Ensemble 的定义文件中就包含了这样的注解，他会要求 Kubernetes 调度器不要把 zk StatefulSet 中的多个 Pod 部署在同一 Node 上。&lt;/p&gt;

&lt;h3 id=&#34;计划内维护的容错&#34;&gt;计划内维护的容错&lt;/h3&gt;

&lt;p&gt;用于创建 ZooKeeper Ensemble 的描述文件还创建了一个 &lt;a href=&#34;http://kubernetes.io/docs/admin/disruptions/&#34; target=&#34;_blank&#34;&gt;PodDistruptionBudget( Pod 中断预算 )&lt;/a&gt; 对象：zk-budget。zk-budget 用于指示 Kubernetes， 这一服务能够容忍的中断 Pod （不健康 Pod）的上限。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;podAntiAffinity&amp;quot;: {
    &amp;quot;requiredDuringSchedulingRequiredDuringExecution&amp;quot;: [
      {
        &amp;quot;labelSelector&amp;quot;: {
          &amp;quot;matchExpressions&amp;quot;: [
            {
              &amp;quot;key&amp;quot;: &amp;quot;app&amp;quot;,
              &amp;quot;operator&amp;quot;: &amp;quot;In&amp;quot;,
              &amp;quot;values&amp;quot;: [
                &amp;quot;zk-headless&amp;quot;
              ]
            }
          ]
        },
        &amp;quot;topologyKey&amp;quot;: &amp;quot;kubernetes.io/hostname&amp;quot;
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl get poddisruptionbudget zk-budget
NAME        MIN-AVAILABLE   ALLOWED-DISRUPTIONS   AGE
zk-budget   2               1                     2h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;zk-budget 定义，至少要有两个处于可用状态的成员才能保障 Ensemble 的健康。如果在离线之前对 Node 进行 Drain 操作，如果这一操作过程中终止的 Pod 会违反预算，Drain 操作就会失败。如果使用 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/kubectl/kubectl_drain/&#34; target=&#34;_blank&#34;&gt;kubectl drain&lt;/a&gt;，来对 Node 进行 cordon 操作并驱逐所有其中运行的 Node，PodDistruption 让你可以确认这一操作不会中断有状态应用的服务。&lt;/p&gt;

&lt;h2 id=&#34;更进一步&#34;&gt;更进一步&lt;/h2&gt;

&lt;p&gt;因为 Kubernetes 的开发工作目标是可用，我们希望获得更多来自用户的设想。如果你想要帮我们处理问题，可以看看 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/labels/area%2Fstateful-apps&#34; target=&#34;_blank&#34;&gt;GitHub 上关于 statful 的 Issues&lt;/a&gt;。然而为了 API 的易于理解，我们并不准备实现所有的功能请求。我们会优先实现一些能够对所有有状态应用产生改善的功能，例如滚动更新支持、Node 升级的集成、使用高速的本地存储等。StatefulSet 的目的在于支持尽可能多而不是全部的有状态应用。基于这些考虑，我们会避免依赖隐藏特性或者技术的方式来充实 StatefulSet。每个人都可以开发一个想 StatefulSets 的控制器。我们称之为 “making it forkable”。明年，我们希望更多的流行的有状态应用能够有自己的社区支持的独立控制器或 &lt;a href=&#34;https://coreos.com/blog/introducing-operators.html&#34; target=&#34;_blank&#34;&gt;&amp;ldquo;操作器&amp;rdquo;&lt;/a&gt;。我们已经听说了 etcd、Redis 和 ZooKeeper 的自定义控制器的开发工作。我们期待更多类似案例不断涌现。&lt;/p&gt;

&lt;p&gt;ETCD 和 Prometheus 的 Operator 来自 CoreOS，演示了一种超出 StatefulSet 能力的自动化和集成能力。另一方面，使用 Deployment 和 StatefulSet 这样的通用控制器能够用同一种对象管理大量有状态应用。Kubernetes 用户有了运行有状态应用的能力，而且可以自行在两种方式之中进行选择。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的容器运行时接口</title>
      <link>/post/container-runtime-interface-cri-in-kubernetes/</link>
      <pubDate>Tue, 20 Dec 2016 22:13:18 +0800</pubDate>
      <guid>/post/container-runtime-interface-cri-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes&#34; target=&#34;_blank&#34;&gt;Introducing Container Runtime Interface (CRI) in Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;文中多次出现了个单词 &lt;code&gt;shim&lt;/code&gt;，胡翻成代理了，虽然垫片还是比鲁棒啥的好听。。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;归根结底，Kubernetes Node 的最底层就是启动和停止容器的组件了，这一部分我们称之为容器运行时（ Container Runtim ），这其中最知名的也就是 Docker 了，这一领域正在快速成长，他并不孤独。为了让 Kubernetes 更具扩展性，我们投入了不少精力，在 Kubernetes 中加入了容器运行时插件 API，我们称之为 “CRI”。&lt;/p&gt;

&lt;h2 id=&#34;cri-是什么-kubernetes-需要他么&#34;&gt;CRI 是什么？Kubernetes 需要他么？&lt;/h2&gt;

&lt;p&gt;每一种容器运行时都有其长处，因此不少用户希望 Kubernetes 能够支持更多的运行时。在 Kubernetes 1.5 中，我们引入了 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/242a97307b34076d5d8f5bbeb154fa4d97c9ef1d/docs/devel/container-runtime-interface.md&#34; target=&#34;_blank&#34;&gt;CRI&lt;/a&gt;，这一插件接口让 Kubernetes 无需重新编译就可以使用更多的容器运行时。CRI 包含 &lt;a href=&#34;https://developers.google.com/protocol-buffers/&#34; target=&#34;_blank&#34;&gt;Protocol Buffers&lt;/a&gt;、&lt;a href=&#34;http://www.grpc.io/&#34; target=&#34;_blank&#34;&gt;gRPC API&lt;/a&gt;、以及&lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/release-1.5/pkg/kubelet/server/streaming/server.go&#34; target=&#34;_blank&#34;&gt;运行库&lt;/a&gt;支持，还有尚在开发的标准规范和工具。 CRI 在 &lt;a href=&#34;http://blog.kubernetes.io/2016/12/kubernetes-1.5-supporting-production-workloads.html&#34; target=&#34;_blank&#34;&gt;Kubernetes 1.5&lt;/a&gt; 中发布了 Alpha 版本。&lt;/p&gt;

&lt;p&gt;可替代的容器运行时支持是 Kubernetes 中的新概念。在 1.3 时，我们发布了 &lt;a href=&#34;http://blog.kubernetes.io/2016/07/rktnetes-brings-rkt-container-engine-to-Kubernetes.html&#34; target=&#34;_blank&#34;&gt;rktnetes&lt;/a&gt; 项目，让 &lt;a href=&#34;https://github.com/coreos/rkt&#34; target=&#34;_blank&#34;&gt;rkt 容器引擎&lt;/a&gt; 成为 Docker 之外的又一选择。然而不管是 Docker 还是 rkt，都是用的 Kubelet 的内部接口，同 Kubelet 源码纠缠不清。这种程度的集成，需要对 Kubelet 内部机制有非常深入的了解，还会给社区带来管理压力。这样就给新生代容器运行时造成了难于跨越的集成壁垒。我们用清晰定义的抽象层清除了这一壁垒，让开发者能够专注于容器运行时本身。在通向插件式容器支持以及建设健康生态环境的路上，这是一小步，也是重要的一步。&lt;/p&gt;

&lt;h2 id=&#34;cri-概览&#34;&gt;CRI 概览&lt;/h2&gt;

&lt;p&gt;Kubelet 使用 gRPC 框架利用 Unix socket 同容器运行时（或者是 CRI 代理）进行通信，这一过程中 Kubelet 是客户端，CRI 代理是服务端。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/overview.png&#34; alt=&#34;OVERVIEW&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Protocol Buffers &lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/release-1.5/pkg/kubelet/api/v1alpha1/runtime/api.proto&#34; target=&#34;_blank&#34;&gt;API&lt;/a&gt; 包含两个 gRPC 服务，&lt;code&gt;ImageService&lt;/code&gt; 和 &lt;code&gt;RuntimeService&lt;/code&gt;。&lt;code&gt;ImageService&lt;/code&gt; 提供从仓库拉取镜像、查看和移除镜像的功能。&lt;code&gt;RuntimeService&lt;/code&gt; 包含了对 Pod 和容器的生命周期管理、和容器的交互（ exec/attach/port-forward ）。rtk 和 Docker 这样的容器运行时可以利用一个 Socket 同时提供两个服务。在 Kubelet 中可以用 &lt;code&gt;--container-runtime-endpoint&lt;/code&gt; 和 &lt;code&gt;--image-service-endpoint&lt;/code&gt; 参数设置这个 socket。&lt;/p&gt;

&lt;h2 id=&#34;pod-和容器的生命周期管理&#34;&gt;Pod 和容器的生命周期管理&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;service RuntimeService {
    // Sandbox operations.
    rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
    rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
    rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}
    rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
    rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {}
    // Container operations.
    rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
    rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
    rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
    rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
    rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
    rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 由一组应用容器组成，其中包含了共有的环境和资源约束。在 CRI 里，这个环境被称为 &lt;code&gt;PodSandbox&lt;/code&gt;。我们有意的给容器运行时留下了一些发挥空间，他们可以根据自己的内部实现来解释 &lt;code&gt;PodSandbox&lt;/code&gt;。对于 Hypervisor 类的运行时，&lt;code&gt;PodSandbox&lt;/code&gt; 会具体化为一个虚拟机。其他的例如 Docker，会是一个 Linux 命名空间。在 v1alpha1 API 中，Kubelet 会创建 Pod 级别的 cgroup 传递给容器运行时，并以此运行所有进程来保障 PodSandbox 对 Pod 的资源保障。&lt;/p&gt;

&lt;p&gt;在启动 Pod 之前，Kubelet 调用 &lt;code&gt;RuntimeService.RunPodSandbox&lt;/code&gt; 来创建环境。这一过程包括为 Pod 设置网络（分配 IP）。&lt;code&gt;PodSandbox&lt;/code&gt; 激活之后，就可以独立的创建、启动、停止和删除不同的容器了。Kubelet 会在停止和删除 &lt;code&gt;PodSandbox&lt;/code&gt; 之前首先停止和删除其中的容器。&lt;/p&gt;

&lt;p&gt;Kubelet 的职责在于通过 RPC 管理容器的生命周期，实现容器生命周期的钩子，以及存活和健康监测，执行 Pod 的重启策略等。&lt;/p&gt;

&lt;h2 id=&#34;为什么-cri-是围绕容器进行的&#34;&gt;为什么 CRI 是围绕容器进行的？&lt;/h2&gt;

&lt;p&gt;Kubernetes 有一个 Pod 资源的接口。我们曾经可能采用的一个 CRI 的设计就是抽象复用 Pod 对象，容器运行时就可以自行实现自己的控制逻辑和状态转换，这样一来，就能极大地简化 API，让 CRI 能够更广泛的适用于多种容器运行时。但是经过深入讨论之后，我们放弃了这一想法。&lt;/p&gt;

&lt;p&gt;首先，Kubelet 有很多的 Pod 级功能和机制（例如循环崩溃的处理），交给容器运行时实现的话，会造成很重的负担；第二，更重要的是，Pod 标准还在高速前进。很多的新功能（例如容器初始化）是由 Kubelet 直接管理容器的，而无需容器运行时进行变更。&lt;/p&gt;

&lt;p&gt;CRI 选择了围绕容器进行实现，这样容器运行时能够共享这些通用特性，获得更好的开发进度。这并不意味着我们设计哲学的改变 —— Kubelet 要负责保证实际状态和声明状态的一致性。&lt;/p&gt;

&lt;h3 id=&#34;exec-attach-port-forward-请求&#34;&gt;Exec/attach/port-forward 请求&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;service RuntimeService {
    ...
    // ExecSync runs a command in a container synchronously.
    rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {}
    // Exec prepares a streaming endpoint to execute a command in the container.
    rpc Exec(ExecRequest) returns (ExecResponse) {}
    // Attach prepares a streaming endpoint to attach to a running container.
    rpc Attach(AttachRequest) returns (AttachResponse) {}
    // PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
    rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {}
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubernetes 为用户提供了和 Pod 以及其中的容器进行交互的能力（&lt;code&gt;kubectl exec/attach/port-forward&lt;/code&gt;）。Kubelet 目前支持两种方式来支持这些功能：调用容器的本地方法，或者使用 Node 上的工具（例如 &lt;code&gt;nsenter&lt;/code&gt; 以及 &lt;code&gt;socat&lt;/code&gt;）。因为多数工具假设 Pod 利用 Linux namespace 做了隔离，因此使用 Node 上的工具并不是一个可移植的方案。在 CRI 中，我们显式的定义这些调用，让运行时可以做特定实现。&lt;/p&gt;

&lt;p&gt;当下还有一个潜在问题是，Kubelet 处理所有的请求连接，所以他有成为 Node 通信瓶颈的可能。在设计 CRI 的时候，我们采纳了一些反馈，让运行时能够排除中间人。容器运行时可以启动一个单独的流服务器处理请求（还能为 Pod 的资源使用进行记录），并把服务器地址返回给 Kubelet。这样 Kubelet 就能反馈信息给 API Server，使之可以直接连接到容器运行时的服务，并连接到客户端。&lt;/p&gt;

&lt;p&gt;CRI 还有很多本文没有提及的内容，可以参考&lt;a href=&#34;https://github.com/kubernetes/community/blob/master/contributors/devel/container-runtime-interface.md#design-docs-and-proposals&#34; target=&#34;_blank&#34;&gt;设计文档&lt;/a&gt;来获得更多内容。&lt;/p&gt;

&lt;h2 id=&#34;当前状态&#34;&gt;当前状态&lt;/h2&gt;

&lt;p&gt;虽然 CRI 还比较初级，但也已经有了很多项目在尝试把各种容器运行时纳入 CRI：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes-incubator/cri-o&#34; target=&#34;_blank&#34;&gt;cri-o&lt;/a&gt;：OCI 兼容运行时&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes-incubator/rktlet&#34; target=&#34;_blank&#34;&gt;rktlet&lt;/a&gt;：rkt 容器运行时。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes/frakti&#34; target=&#34;_blank&#34;&gt;frakti&lt;/a&gt;：基于 hypervisor 的容器运行时。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes/kubernetes/tree/release-1.5/pkg/kubelet/dockershim&#34; target=&#34;_blank&#34;&gt;Docker CRI 代理&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果对这些运行时感兴趣，可以浏览一下他们的仓库，获取最新的进展情况和相关资料。&lt;/p&gt;

&lt;p&gt;开发者如果有兴趣集成新的容器运行时，需要阅读&lt;a href=&#34;https://github.com/kubernetes/community/blob/master/contributors/devel/container-runtime-interface.md&#34; target=&#34;_blank&#34;&gt;开发者指南&lt;/a&gt;，会在这里得到 API 中已知的限制和问题。我们乐于从早期开发者这里获取反馈，加以改进来促进项目成长。开发者需要有对突发故障的心理准备，谁让我们还在 Alpha 呢。&lt;/p&gt;

&lt;h2 id=&#34;尝试新的-cri-docker&#34;&gt;尝试新的 CRI Docker&lt;/h2&gt;

&lt;p&gt;Kubelet 还没有把 CRI 作为缺省选项，我们正在积极促成这一转变。第一步就是用 CRI 的方式重新对 Docker 进行 Kubelet 的集成。在 1.5 中，我们让 Kubelet 开始支持 CRI，还给 Kubelet 加入了内嵌的 Docker CRI 代理。这样 Kubelet 就可以启动 Docker 的 gRPC 服务了。要尝试新的 Kubelet-CRI-Docker 集成，只需要简单的给 API-Server 参数加上 &lt;code&gt;--feature-gates=StreamingProxyRedirects=true&lt;/code&gt; 开关，就启用了新的请求重定向特性，然后用 &lt;code&gt;--experimental-cri=true&lt;/code&gt; 开关来启动 Kubelet。&lt;/p&gt;

&lt;p&gt;虽说现在的实现还有少量&lt;a href=&#34;https://github.com/kubernetes/community/blob/master/contributors/devel/container-runtime-interface.md#docker-cri-integration-known-issues&#34; target=&#34;_blank&#34;&gt;功能缺失&lt;/a&gt;，不过已经通过了主要的端到端测试，我们计划扩展测试覆盖范围，也再次邀请社区多多提供反馈来促进我们的工作。&lt;/p&gt;

&lt;h2 id=&#34;minikube-和-cri&#34;&gt;Minikube 和 CRI&lt;/h2&gt;

&lt;p&gt;如果想要测试新功能，却没时间部署新的测试集群。&lt;a href=&#34;https://github.com/kubernetes/minikube&#34; target=&#34;_blank&#34;&gt;Minikube&lt;/a&gt; 让你能够快速的启动一个本地集群。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;检查可用的 Kubernetes 版本，选择最新的 1.5.x，这里使用的是 v1.5.9-beta.1：&lt;code&gt;minikube get-k8s-versions&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;启动一个带有内置 Docker CRI 集成的 Minikube 集群：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ minikube start --kubernetes-version=v1.5.0-beta.1  \
--extra-config=kubelet.EnableCRI=true \
--network-plugin=kubenet \
--extra-config=kubelet.PodCIDR=10.180.1.0/24 \
--iso-url=http://storage.googleapis.com/minikube/iso/buildroot/minikube-v0.0.6.iso
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;--extra-config=kubelet.EnableCRI=true&lt;/code&gt; 启用 Kubelet 的 CRI 实现，&lt;code&gt;--network-plugin=kubenet&lt;/code&gt; 和 &lt;code&gt;--extra-config=kubelet.PodCIDR=10.180.1.0/24&lt;/code&gt; 为网络插件提供了网络设置，分配 PodCIDR 给 Node。这里也可以使用 &lt;code&gt;cni&lt;/code&gt; 插件，就无需依赖 PodCIDR 了。&lt;code&gt;--iso-url&lt;/code&gt; 给 Minikube 指定一个例子中使用的 ISO 镜像。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;检查 Minikube 日志，确认 CRI 的启用&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ minikube logs | grep EnableCRI
I1209 01:48:51.150789    3226 localkube.go:116] Setting EnableCRI to true on kubelet.
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;创建一个 Pod 并检查状态，应该会看到 &amp;ldquo;SandboxReceived&amp;rdquo; 事件，这表明 Kubelet 正在使用 CRI&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl run foo --image=gcr.io/google_containers/pause-amd64:3.0
deployment &amp;quot;foo&amp;quot; created
$ kubectl describe pod foo
...
... From                Type   Reason          Message
... -----------------   -----  --------------- -----------------------------
...{default-scheduler } Normal Scheduled       Successfully assigned foo-141968229-v1op9 to minikube
...{kubelet minikube}   Normal SandboxReceived Pod sandbox received, it will be created.
...
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;注意 &lt;code&gt;kubectl attach/exec/port-forward&lt;/code&gt; 目前还不能对启用 CRI 模式的 Miniqube 生效，&lt;a href=&#34;https://github.com/kubernetes/minikube/issues/896&#34; target=&#34;_blank&#34;&gt;新版本&lt;/a&gt;将会加入支持。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;相关引用&#34;&gt;相关引用&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CRI&lt;/strong&gt;：&lt;code&gt;https://github.com/kubernetes/kubernetes/blob/242a97307b34076d5d8f5bbeb154fa4d97c9ef1d/docs/devel/container-runtime-interface.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cri-o&lt;/strong&gt;：&lt;code&gt;https://github.com/kubernetes-incubator/cri-o&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rktlet&lt;/strong&gt;：&lt;code&gt;https://github.com/kubernetes-incubator/rktlet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;frakti&lt;/strong&gt;：&lt;code&gt;https://github.com/kubernetes/frakti&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker CRI 代理&lt;/strong&gt;：&lt;code&gt;https://github.com/kubernetes/kubernetes/tree/release-1.5/pkg/kubelet/dockershim&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kompose: Docker-compose 到 Kubernetes 的迁移工具</title>
      <link>/post/kompose-compose-to-kubernetes/</link>
      <pubDate>Wed, 23 Nov 2016 09:26:57 +0800</pubDate>
      <guid>/post/kompose-compose-to-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/11/kompose-tool-go-from-docker-compose-to-kubernetes&#34; target=&#34;_blank&#34;&gt;Kompose: a tool to go from Docker-compose to Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在 &lt;a href=&#34;http://www.skippbox.com/&#34; target=&#34;_blank&#34;&gt;skippbox&lt;/a&gt;，我们开发了 &lt;strong&gt;Kompose&lt;/strong&gt; 这一工具，他能够自动把 Docker Compose 应用转换为 Kubernetes 描述文件。利用一个简单的 &lt;code&gt;kompose up&lt;/code&gt; 命令，就可以在 Kubernetes 集群上启动 Compose 应用。我们非常乐于将其捐献给 &lt;a href=&#34;https://github.com/kubernetes-incubator&#34; target=&#34;_blank&#34;&gt;Kubernetes Incubator&lt;/a&gt;。下面介绍一下这一工具的开发动机和用法。&lt;/p&gt;

&lt;p&gt;Docker 给了开发者以巨大的帮助。让每个人都能够从 Docker Registry 启动一个打包好的 Docker 应用。为了对付多容器应用， Docker 开发了 Docker-compose （也就是 Compose）。Compose 借助 yaml 格式的描述文件来定义一个多容器应用，然后就可以用一个简单的 &lt;code&gt;docker-compose up&lt;/code&gt; 来启动这一应用中的多个容器。然而，Compose 只能够在本地或者 Docker Swarm 集群中运行。&lt;/p&gt;

&lt;p&gt;那如果我们需要在 Swarm 之外运行怎么办？比如 Kubernetes？&lt;/p&gt;

&lt;p&gt;Compose 格式并非为分布式而诞生的。所以，你只能为你选择的容器编排工具重新编写应用描述文件。&lt;/p&gt;

&lt;p&gt;我们利用 Kompose，能够简单的完成将应用从 Docker Swarm 到 Kubernetes 的转换过程，这样就为 Docker 用户敞开了 Kubernetes 的大门。&lt;/p&gt;

&lt;p&gt;今年夏天，来自红帽子的  Tomas Kral 和 Suraj Deshmukh，以及来自 Google 的 Janet Kuo，他们和 Kompose 的主要开发者 Nguyen An-Tu 一起为 Kompose 锦上添花。我们把 Kompose 提交给 Kubernets Incubator，得到了 Kubernetes 社区的支持，现在可以在 &lt;a href=&#34;https://github.com/kubernetes-incubator/kompose&#34; target=&#34;_blank&#34;&gt;Kubernetes Incubator&lt;/a&gt; 找到 Kompose。&lt;/p&gt;

&lt;p&gt;Kompose 目前支持 Docker-compose v2 格式，最近还加入了持久卷所有权（PVC）、以及多容器 Pod 的支持。除了缺省的 Kubernetes 之外，我们还支持 Openshift 的发布能力。Kompose 现在还出现在了 Fedora 包中，未来也会进入 CentOS 中去。&lt;/p&gt;

&lt;p&gt;Kompose 是一个 Golang 应用，可以从 &lt;a href=&#34;https://github.com/kubernetes-incubator/kompose&#34; target=&#34;_blank&#34;&gt;Github&lt;/a&gt; 上获取。下面让我们跳过 Build 环节直接进入实例。&lt;/p&gt;

&lt;h2 id=&#34;docker-的留言板应用&#34;&gt;Docker 的留言板应用&lt;/h2&gt;

&lt;p&gt;留言板应用是 Kubernetes 的权威示例。如果要用 Docker Compose 来实现留言板，可以用下面的代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;version: &amp;quot;2&amp;quot;

services:
  redis-master:
    image: gcr.io/google_containers/redis:e2e
    ports:
      - &amp;quot;6379&amp;quot;
  redis-slave:
    image: gcr.io/google_samples/gb-redisslave:v1
    ports:
      - &amp;quot;6379&amp;quot;
    environment:
      - GET_HOSTS_FROM=dns
  frontend:
    image: gcr.io/google-samples/gb-frontend:v4
    ports:
      - &amp;quot;80:80&amp;quot;
    environment:
      - GET_HOSTS_FROM=dns
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中包含了三个服务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一个 Redis 主节点；&lt;/li&gt;
&lt;li&gt;一组能够横向扩展并借助 DNS 找到 Master 的 Redis 从节点；&lt;/li&gt;
&lt;li&gt;暴露于 80 端口的 PHP 前端。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些组合在一起，让用户可以发表留言，并保存在 Redis 集群中。&lt;/p&gt;

&lt;p&gt;要启动这个应用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ docker-compose -f docker-guestbook.yml up -d
Creating network &amp;quot;examples_default&amp;quot; with the default driver
Creating examples_redis-slave_1
Creating examples_frontend_1
Creating examples_redis-master_1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这就是一个简单的 Docker 用法，下面我肯看看如何在不重写任何东西的情况下，让这些工作在 Kubernetes 上完成。&lt;/p&gt;

&lt;h2 id=&#34;kompose-的留言板应用&#34;&gt;Kompose 的留言板应用&lt;/h2&gt;

&lt;p&gt;Kompose 目前有三个主要的命令：&lt;code&gt;up&lt;/code&gt;、&lt;code&gt;down&lt;/code&gt; 以及 &lt;code&gt;convert&lt;/code&gt;。为了行文方便，我们只简单说一下留言吧应用的启动。&lt;/p&gt;

&lt;p&gt;跟 &lt;code&gt;docker-compose&lt;/code&gt; 类似，我们可以用 &lt;code&gt;kompose up&lt;/code&gt; 命令处理 Docker compose 文件，来启动应用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ kompose -f ./examples/docker-guestbook.yml up
We are going to create Kubernetes deployment and service for your dockerized application.
If you need more kind of controllers, use &#39;kompose convert&#39; and &#39;kubectl create -f&#39; instead.

INFO[0000] Successfully created service: redis-master
INFO[0000] Successfully created service: redis-slave
INFO[0000] Successfully created service: frontend
INFO[0000] Successfully created deployment: redis-master
INFO[0000] Successfully created deployment: redis-slave
INFO[0000] Successfully created deployment: frontend

Application has been deployed to Kubernetes. You can run &#39;kubectl get deployment,svc&#39; for details.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kompose 自动把 Docker-compose 文件转为 Kuberntes 对象。缺省情况下，他会为一个 Compose 服务创建一个 Deployment 以及一个服务。另外还能自动检测当前的 Kuberntes 端点，并在上面创建对象。可以通过一系列的选项来创建 Replication Controller、Replica Set 或者 Daemon Set。&lt;/p&gt;

&lt;p&gt;就这样完成了自动转换，如果你了解一些 Kubernetes 的话，可以用 kubectl 命令来看看集群上运行的留言板。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ kubectl get pods,svc,deployments
NAME                             READY        STATUS        RESTARTS     AGE
frontend-3780173733-0ayyx        1/1          Running       0            1m
redis-master-3028862641-8miqn    1/1          Running       0            1m
redis-slave-3788432149-t3ejp     1/1          Running       0            1m
NAME                             CLUSTER-IP   EXTERNAL-IP   PORT(S)      AGE
frontend                         10.0.0.34    &amp;lt;none&amp;gt;        80/TCP       1m
redis-master                     10.0.0.219   &amp;lt;none&amp;gt;        6379/TCP     1m
redis-slave                      10.0.0.84    &amp;lt;none&amp;gt;        6379/TCP     1m
NAME                             DESIRED      CURRENT       UP-TO-DATE 

AVAILABLE   AGE
frontend                         1            1             1            1           1m
redis-master                     1            1             1            1           1m
redis-slave                      1            1             1            1           1m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到了三个服务、三个 Deployment 以及三个 Pod。可以通过 frontend 服务来访问留言板应用。只不过这次的留言板，是从 Docker-Compose 文件启动的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/guestbook.png&#34; alt=&#34;guestbook&#34; /&gt;&lt;/p&gt;

&lt;p&gt;以上给读者快速的介绍了一下 kompose。还有很多激动人心的特性，例如创建不同类型的资源、创建 Helm Chars，甚至可以使用试验性的 Docker bundle 格式进行输入（Lachlan Evenson 的博客：&lt;a href=&#34;https://deis.com/blog/2016/push-docker-dab-kubernetes-cluster/&#34; target=&#34;_blank&#34;&gt;using a Docker bundle with Kubernetes&lt;/a&gt;）。可以在我们的 &lt;a href=&#34;https://www.youtube.com/watch?v=zqUfPPNVjI8&amp;amp;index=42&amp;amp;list=PLj6h78yzYM2PqgIGU1Qmi8nY7dqn9PCr4&#34; target=&#34;_blank&#34;&gt;KubeCon 上的视频&lt;/a&gt; 中看到完整的演示。&lt;/p&gt;

&lt;p&gt;前往 &lt;a href=&#34;https://github.com/kubernetes-incubator/kompose&#34; target=&#34;_blank&#34;&gt;Kubernetes incubator&lt;/a&gt; 获取 Kompose，可以帮助你轻松地把应用从 Docker Compose 迁移为 Kubernetes 集群应用。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的 StorageClass 和动态卷供给</title>
      <link>/post/dynamic-provision-for-storage-volumes-in-kubernetes/</link>
      <pubDate>Thu, 13 Oct 2016 02:36:21 +0800</pubDate>
      <guid>/post/dynamic-provision-for-storage-volumes-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;hhttps://kubernetes.io/blog/2016/10/dynamic-provisioning-and-storage-in-kubernetes&#34; target=&#34;_blank&#34;&gt;Dynamic Provisioning and Storage Classes in Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;存储是容器运行环境的重要一环，Kubernetes 提供了一些用于存储管理的基础能力。动态卷供给是一个 Kubernetes 独有的功能，这一功能允许按需创建存储卷。在没有这种能力之前，集群管理员需要打电话给他们的云或者存储提供者来创建新的存储卷，成功以后再创建 &lt;code&gt;PersistentVolume&lt;/code&gt; 对象，才能够在 Kubernetes 中使用。动态卷供给能力让管理员不必进行预先创建存储卷，而是随用户需求进行创建。这一特性在 1.2 版本中处于 α 阶段，在 &lt;a href=&#34;http://blog.kubernetes.io/2016/09/kubernetes-1.4-making-it-easy-to-run-on-kuberentes-anywhere.html&#34; target=&#34;_blank&#34;&gt;版本 1.4&lt;/a&gt; 中提升为 β。这一版本提高了动态卷的弹性和可用性。&lt;/p&gt;

&lt;h2 id=&#34;新特性&#34;&gt;新特性&lt;/h2&gt;

&lt;p&gt;Alpha 版本的动态卷，一个集群同时只能允许单独的、被硬编码的提供者。也就是说，如果 Kubernetes 要提供动态卷的时候，即使集群中可以使用多个存储系统，Kubernetes 也只会使用同一个存储卷插件。存储提供者的选型是基于云环境类型决定的 —— AWS 的 EBS，Google Cloud 的 Persistent Disk 或者是 OpenStack 的 Cinder，以及 vSphere 的 vSphere Volume。另外只有容量参数可以配置。这就意味着，即使有其他参数可用，所有的动态卷除了尺寸大小，其他都是一样的。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;因为只有容量是大家都有的吧。。。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;虽说这一功能的 Alpha 版本实用性有限，这毕竟是迈出了一步，有助于确定今后的发展方向。&lt;/p&gt;

&lt;p&gt;Kubernetes 1.4 中中加入了一个 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses&#34; target=&#34;_blank&#34;&gt;新的 API 对象&lt;/a&gt; &lt;code&gt;StorageClass&lt;/code&gt;，可以定义多个 &lt;code&gt;StorageClass&lt;/code&gt; 对象，并可以分别指定存储插件、设置参数，用于提供不同的存储卷。这样的设计让集群管理员能够在同一个集群内，定义和提供不同类型的、不同参数的卷（相同或者不同的存储系统）。这样的设计还确保了最终用户在无需了解太多的情况下，有能力选择不同的存储选项。&lt;/p&gt;

&lt;h2 id=&#34;如何使用&#34;&gt;如何使用&lt;/h2&gt;

&lt;p&gt;下面是一个例子，管理员提供了两种存储，用户可以选择其中一个使用。细节可以查看 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses&#34; target=&#34;_blank&#34;&gt;手册&lt;/a&gt; 以及 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/tree/release-1.4/examples/experimental/persistent-volume-provisioning&#34; target=&#34;_blank&#34;&gt;示例&lt;/a&gt; 文档。&lt;/p&gt;

&lt;h3 id=&#34;管理员配置&#34;&gt;管理员配置&lt;/h3&gt;

&lt;p&gt;集群管理员定义并发布了两个 &lt;code&gt;StorageClass&lt;/code&gt; 对象&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: slow
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;这一段创建了一个名为 &amp;ldquo;slow&amp;rdquo; 的 &lt;code&gt;StorageClass&lt;/code&gt;，用于提供标准的持久存储。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: fast
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;这一段创建了一个名为 &amp;ldquo;fast&amp;rdquo; 的 &lt;code&gt;StorageClass&lt;/code&gt;，用于提供类似 SSD 的持久存储。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;用户请求&#34;&gt;用户请求&lt;/h3&gt;

&lt;p&gt;用户在 &lt;code&gt;PersistentVolumeClaim&lt;/code&gt; 中可以包含一个 &lt;code&gt;StorageClass&lt;/code&gt; 申请动态提供存储。这一任务需要使用 &lt;code&gt;volume.beta.kubernetes.io/storage-class&lt;/code&gt; 注解来完成。这一注解的值必须符合管理员配置的 &lt;code&gt;StorageClass&lt;/code&gt; 名称。&lt;/p&gt;

&lt;p&gt;要选择 &amp;ldquo;fast&amp;rdquo; 存储类，用户需要创建如下的 PVC：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt; {
  &amp;quot;kind&amp;quot;: &amp;quot;PersistentVolumeClaim&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;metadata&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;claim1&amp;quot;,
    &amp;quot;annotations&amp;quot;: {
        &amp;quot;volume.beta.kubernetes.io/storage-class&amp;quot;: &amp;quot;fast&amp;quot;
    }
  },
  &amp;quot;spec&amp;quot;: {
    &amp;quot;accessModes&amp;quot;: [
      &amp;quot;ReadWriteOnce&amp;quot;
    ],
    &amp;quot;resources&amp;quot;: {
      &amp;quot;requests&amp;quot;: {
        &amp;quot;storage&amp;quot;: &amp;quot;30Gi&amp;quot;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上述报文会提供一个等效于 SSD 的持久盘，当这个 PVC 被删除，这个卷也随之销毁。&lt;/p&gt;

&lt;h3 id=&#34;缺省行为&#34;&gt;缺省行为&lt;/h3&gt;

&lt;p&gt;所有的 PVC 都可以在不使用 &lt;code&gt;StorageClass&lt;/code&gt; 注解的情况下，直接使用某个动态存储。把一个 &lt;code&gt;StorageClass&lt;/code&gt; 对象标记为 &amp;ldquo;default&amp;rdquo; 就可以了。&lt;code&gt;StorageClass&lt;/code&gt; 用注解 &lt;code&gt;storageclass.beta.kubernetes.io/is-default-class&lt;/code&gt; 就可以成为缺省存储。&lt;/p&gt;

&lt;p&gt;有了缺省的 &lt;code&gt;StorageClass&lt;/code&gt;，用户创建 PVC 就不用 storage-class 的注解了，1.4 中新加入的 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/30900&#34; target=&#34;_blank&#34;&gt;DefaultStorageClass&lt;/a&gt; 准入控制器会自动把这个标注指向缺省存储类。&lt;/p&gt;

&lt;h3 id=&#34;我还能使用-alpha-版本么&#34;&gt;我还能使用 Alpha 版本么？&lt;/h3&gt;

&lt;p&gt;Kubernetes 1.4 兼容 alpha 版本的动态卷特性，让用户能够平滑过渡到 beta 版本。用 &lt;code&gt;volume.alpha.kubernetes.io/storage-class&lt;/code&gt; 注解来标注 alpha 版本。&lt;/p&gt;

&lt;p&gt;在未来版本中将会弃用 Alpha 版本。&lt;/p&gt;

&lt;h3 id=&#34;下一步&#34;&gt;下一步？&lt;/h3&gt;

&lt;p&gt;动态卷功能会持续发展，下面是一些要点。&lt;/p&gt;

&lt;h4 id=&#34;标准云支持&#34;&gt;标准云支持&lt;/h4&gt;

&lt;p&gt;如果 Kubernetes 集群部署在云服务商，我们 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/31617/files&#34; target=&#34;_blank&#34;&gt;考虑&lt;/a&gt; 自动使用云的本地存储系统创建一个动态卷供给者。例如在 AWS 上的标准部署会得到一个 EBS 的动态卷供给，而 Google Cloud 的部署则会提供一个 GCE PD 动态卷供应者。我们还在讨论是否应该把这种卷作为缺省卷。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 部署安全最佳实践</title>
      <link>/post/best-practice-for-deployment-security-in-kubernetes/</link>
      <pubDate>Tue, 27 Sep 2016 06:56:57 +0800</pubDate>
      <guid>/post/best-practice-for-deployment-security-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/08/security-best-practices-kubernetes-deployment&#34; target=&#34;_blank&#34;&gt;Security Best Practices for Kubernetes Deployment&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文作者是来自 Aqua Security 的  Amir Jerbi 和 Michael Cherny，他们以大量的案例和经验为基础，总结并描述了 Kubernetes 部署中的最佳安全实践。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes 提供了很多能够提高应用安全的方法。要进行这些配置，就要掌握 Kubernetes 的相关知识，同时也要清楚的了解安全需求。这里我们关注的安全内容集中在容器的生命周期上：构建、传输以及运行，并且针对 Kubernetes 进行了特别的裁剪。&lt;a href=&#34;http://blog.aquasec.com/running-a-security-service-in-google-cloud-real-world-example&#34; target=&#34;_blank&#34;&gt;我们自己的 SaaS&lt;/a&gt; 就是运行在 Google Cloud Platform 上的 Kubernetes 中，已经采用了这些最佳实践。&lt;/p&gt;

&lt;p&gt;下面是我们对于安全部署 Kubernetes 应用的一些建议。&lt;/p&gt;

&lt;h2 id=&#34;确保镜像无漏洞&#34;&gt;确保镜像无漏洞&lt;/h2&gt;

&lt;p&gt;运行带有漏洞的容器会让你的环境身处险境。只要运行中的系统的所有组件都不存在已知漏洞，就能够避免很多被攻击的机会。&lt;/p&gt;

&lt;h3 id=&#34;安全漏洞的持续扫描&#34;&gt;安全漏洞的持续扫描&lt;/h3&gt;

&lt;p&gt;容器中可能有一些过期组件，这些过期组件往往会包含已知漏洞（CVE）。新的漏洞层出不穷，因此对安全漏洞的扫描工作必须持续进行。&lt;/p&gt;

&lt;h3 id=&#34;适时应用安全更新&#34;&gt;适时应用安全更新&lt;/h3&gt;

&lt;p&gt;一旦在运行的容器中发现了安全漏洞，就该对源镜像进行更新并部署。为了避免破坏镜像和容器的继承性，尽量不要在容器&lt;strong&gt;中&lt;/strong&gt;直接进行更新（例如 &lt;code&gt;apt-update&lt;/code&gt;）。 Kubernetes 的滚动更新功能可以渐进式的为运行中的应用更新镜像，这一功能让应用更新变得简单优雅。&lt;/p&gt;

&lt;h2 id=&#34;只使用可靠的镜像&#34;&gt;只使用可靠的镜像&lt;/h2&gt;

&lt;p&gt;要避免受到有漏洞甚至恶意的容器的威胁，镜像的准入就需要受到有效管理。和随意下载运行软件一样，下载运行不可靠的镜像也是高危行为，必须杜绝。&lt;/p&gt;

&lt;p&gt;使用私库来保存你的镜像，并保证只向其推送可靠镜像。这样就缩小了战场面积，避免大量不确认的公开镜像涌入你的环境。另外建议在持续构建流程中加入漏洞扫描之类的安全环节。&lt;/p&gt;

&lt;p&gt;持续集成管线要控制门槛，只允许使用受确认的代码进行镜像构建。镜像构建成功后，应该进行漏洞扫描，排除问题后才能推入私库，进行下一步的部署。过程中发现问题，应该终端构建过程，阻止安全质量低下的镜像进入私库。&lt;/p&gt;

&lt;p&gt;目前 Kubernetes 正在开发镜像认证插件（将在 1.4 推出），用以阻挡未认证镜像的进入，相关信息请参看 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/27129&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;pull request&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;限制对-kubernetes-node-的直接访问&#34;&gt;限制对 Kubernetes Node 的直接访问&lt;/h2&gt;

&lt;p&gt;对 Kubernetes Node 的 SSH 访问会降低主机的安全性。应该让用户尽量使用 &lt;code&gt;kubectl exec&lt;/code&gt;，这一命令提供了对容器环境的直接访问，而不需要接触宿主机。&lt;/p&gt;

&lt;p&gt;还可以使用 Kubernetes 的 &lt;a href=&#34;http://kubernetes.io/docs/admin/authorization/&#34; target=&#34;_blank&#34;&gt;Authorization Plugins&lt;/a&gt; 来对用户的资源访问进行进一步控制。这一插件允许定义对命名空间、容器以及操作的基于角色的访问控制。&lt;/p&gt;

&lt;h2 id=&#34;在资源之间建立管理边界&#34;&gt;在资源之间建立管理边界&lt;/h2&gt;

&lt;p&gt;限制用户权限能够降低出错和入侵造成的危害。Kubernetes 命名空间让你可以把资源分割为不同名称的群组之中。一个命名空间中创建的资源对其他命名空间是不可见的。缺省情况下，Kubernetes 用户创建的资源都存在于 default 命名空间中。可以创建其他的命名空间，并把资源和用户绑定上去。可以使用 Kubernetes Authorization 插件来创建策略，让不同用户分别访问各自的命名空间和对应的资源。&lt;/p&gt;

&lt;p&gt;例如下面的策略让 &lt;strong&gt;&amp;ldquo;Alice&amp;rdquo;&lt;/strong&gt; 能够从命名空间 &lt;strong&gt;&amp;ldquo;fronto&amp;rdquo;&lt;/strong&gt; 中读取 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;{
  &amp;quot;apiVersion&amp;quot;: &amp;quot;abac.authorization.kubernetes.io/v1beta1&amp;quot;, 
  &amp;quot;kind&amp;quot;: &amp;quot;Policy&amp;quot;, 
  &amp;quot;spec&amp;quot;: {
    &amp;quot;user&amp;quot;: &amp;quot;alice&amp;quot;,
    &amp;quot;namespace&amp;quot;: &amp;quot;fronto&amp;quot;,
    &amp;quot;resource&amp;quot;: &amp;quot;pods&amp;quot;,
    &amp;quot;readonly&amp;quot;: true
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;设定资源配额&#34;&gt;设定资源配额&lt;/h2&gt;

&lt;p&gt;容器运行中如果没有资源限制，那么系统就可能处于 &lt;strong&gt;DoS&lt;/strong&gt; 或&lt;a href=&#34;https://en.wikipedia.org/wiki/Cloud_computing_issues#Performance_interference_and_noisy_neighbors&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;邻里不和&lt;/strong&gt;&lt;/a&gt;的情境之中。要降低或阻止这一风险，就需要设定资源配额。缺省情况下，所有的 Kubernetes 集群资源都可以不受限的访问 CPU 和内存。可以为命名空间创建配额策略，来限制 Pod 的 CPU 和内存消费。&lt;/p&gt;

&lt;p&gt;下面的例子是一个命名空间的资源配额定义，限制运行 Pod 数量为 4，CPU 的使用限制在 1-2 之间，内存使用在 1-2 G 之间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resources
spec:
  hard:
    pods: &amp;quot;4&amp;quot;
    requests.cpu: &amp;quot;1&amp;quot;
    requests.memory: 1Gi
    limits.cpu: &amp;quot;2&amp;quot;
    limits.memory: 2Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;将资源配额指派给命名空间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl create -f ./compute-resources.yaml --namespace=myspace
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;规划网络分区&#34;&gt;规划网络分区&lt;/h2&gt;

&lt;p&gt;在同一个 Kubernetes 集群上运行不同的应用，引入了一个风险就是应用之间的互相访问。要确保容器只能访问允许访问的范围，网络分区是很重要的。Kubernetes 中的一大挑战就是在 Pod、Service 以及容器之间的网络划分，造成这一问题的根本在于容器网络的动态分配过程，让容器可以跨越 Node 进行网络互访。&lt;/p&gt;

&lt;p&gt;Google Cloud Platform 用户收益于自动防火墙规则功能，能够阻止跨集群的通信。使用 SDN 或者防火墙能够达到类似的效果。Kuberntes &lt;a href=&#34;https://github.com/kubernetes/community/wiki/SIG-Network&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;Network SIG&lt;/strong&gt;&lt;/a&gt; 正在进行这方面的努力，目的是增强 Pod 之间的通信策略。新的网络策略 API 将会用于创建 Pod 之间的防火墙规则，限制容器应用的网络访问。&lt;/p&gt;

&lt;p&gt;下面的例子是一条网络策略，用于控制 &amp;ldquo;backend&amp;rdquo; Pod，只允许来自于 &amp;ldquo;frontend&amp;rdquo; Pod 的访问。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;POST /apis/net.alpha.kubernetes.io/v1alpha1/namespaces/tenant-a/networkpolicys
{
  &amp;quot;kind&amp;quot;: &amp;quot;NetworkPolicy&amp;quot;,
  &amp;quot;metadata&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;pol1&amp;quot;
  },
  &amp;quot;spec&amp;quot;: {
    &amp;quot;allowIncoming&amp;quot;: {
      &amp;quot;from&amp;quot;: [{
        &amp;quot;pods&amp;quot;: { &amp;quot;segment&amp;quot;: &amp;quot;frontend&amp;quot; } 
      }],
      &amp;quot;toPorts&amp;quot;: [{
        &amp;quot;port&amp;quot;: 80,
        &amp;quot;protocol&amp;quot;: &amp;quot;TCP&amp;quot; 
      }]
    },
    &amp;quot;podSelector&amp;quot;: { 
      &amp;quot;segment&amp;quot;: &amp;quot;backend&amp;quot; 
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;网络策略的更多信息可以阅读 &lt;a href=&#34;http://blog.kubernetes.io/2016/04/Kubernetes-Network-Policy-APIs.html&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;SIG-Networking: Kubernetes Network Policy APIs Coming in 1.3&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;pod-和容器的安全上下文&#34;&gt;Pod 和容器的安全上下文&lt;/h2&gt;

&lt;p&gt;设计容器和 Pod 的时候，一定要配置 Pod、容器以及卷的安全上下文。安全上下文是部署 Yaml 中的一个属性，他控制了 pod/container/volume 的安全参数，下面列出一些重要的参数：&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;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SecurityContext-&amp;gt;runAsNonRoot&lt;/td&gt;
&lt;td&gt;容器应该用非 root 用户运行&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;SecurityContext-&amp;gt;Capabilities&lt;/td&gt;
&lt;td&gt;设置 Linux 分配给容器的性能&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;SecurityContext-&amp;gt;readOnlyRootFilesystem&lt;/td&gt;
&lt;td&gt;容器是否可以写入 root 文件系统&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;PodSecurityContext-&amp;gt;runAsNonRoot&lt;/td&gt;
&lt;td&gt;阻止 Pod 中的容器以 root 用户运行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;下面是一个带有安全上下文的 Pod 定义：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: hello-world
spec:
  containers:
  # specification of the pod’s containers
  # ...
  securityContext:
    readOnlyRootFilesystem: true
    runAsNonRoot: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&#34;http://kubernetes.io/docs/api-reference/v1/definitions/#_v1_podsecuritycontext&#34; target=&#34;_blank&#34;&gt;参考&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果用特权形式（&lt;code&gt;--privileged&lt;/code&gt;）运行容器，可以用 &lt;code&gt;DenyEscalatingExec&lt;/code&gt; 控制。这一开关拒绝在特权容器上使用 Exec 和 Attach 命令。具体情况可以参考 &lt;a href=&#34;http://kubernetes.io/docs/admin/admission-controllers/&#34; target=&#34;_blank&#34;&gt;Admission 文档&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;记录日志&#34;&gt;记录日志&lt;/h2&gt;

&lt;p&gt;Kubernetes 支持集群级别的日志，集中收集日志到中央服务。当集群创建之后，STDOUT 和 STDERR 就能够被 Node 中的 Fluent 搜集起来，并汇总到 Google Stackdriver Logging 或者 Elasticsearch，并用 Kibana 进行查看。&lt;/p&gt;

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

&lt;p&gt;Kubernetes 为安全提供了很多特性。对这些特性进行学习和了解，才能够制定出符合应用需求的安全方案。&lt;/p&gt;

&lt;p&gt;我们建议实施文中提到的最佳实践，使用 Kubernetes 的动态配置能力，结合持续集成，无缝提高安全保障能力。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Minikube：轻松运行本地 Kuberntes 集群</title>
      <link>/post/minikube-easily-run-kubernetes-locally/</link>
      <pubDate>Thu, 14 Jul 2016 15:49:24 +0800</pubDate>
      <guid>/post/minikube-easily-run-kubernetes-locally/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/07/minikube-easily-run-kubernetes-locally&#34; target=&#34;_blank&#34;&gt;Minikube: easily run Kubernetes locally&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 已经是目前最佳的应用容器化工具之一。在投入实际使用之后的一年来，Kubernetes（社区）需要一个本地开发平台。&lt;/p&gt;

&lt;p&gt;在过去的几个月中，Kubernetes 社区中的很多成员在为 Github 的 &lt;a href=&#34;http://github.com/kubernetes/minikube&#34; target=&#34;_blank&#34;&gt;Minikube 仓库&lt;/a&gt; 奋战。我们要建立一个可以一键运行的版本，这个版本要易用和准确兼顾，并且能够兼容 Mac、 Linux 以及 Windows 的工作站和笔记本电脑。&lt;/p&gt;

&lt;p&gt;感谢社区成员的贡献，我们自豪的宣告 Minikube 的诞生，并与 &lt;a href=&#34;http://blog.kubernetes.io/2016/07/kubernetes-1.3-bridging-cloud-native-and-enterprise-workloads.html&#34; target=&#34;_blank&#34;&gt;Kubernetes 1.3&lt;/a&gt; 一起发布，新的命令能够更好的跟本地集群通信，并且加入了试验性的对 xhyve（OS X）以及 KVM（Linux）的支持。&lt;/p&gt;

&lt;h2 id=&#34;使用-minikube&#34;&gt;使用 Minikube&lt;/h2&gt;

&lt;p&gt;Minitube 是一个独立的 Go 应用，所以只要下载运行就可以了：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Minikube 目前要求安装 &lt;a href=&#34;https://www.virtualbox.org/&#34; target=&#34;_blank&#34;&gt;VirtualBox&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 这是针对 Mac， 如果是 Linux 的话，用 minikube-linux-amd64 代替 minikube-darwin-amd64 即可

curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64 &amp;amp;&amp;amp; chmod +x minikube &amp;amp;&amp;amp; sudo mv minikube /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;要启动 Kubernetes 集群，要使用&lt;code&gt;minikube start&lt;/code&gt;命令：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ minikube start
Starting local Kubernetes cluster...
Kubernetes is available at https://192.168.99.100:443
Kubectl is now configured to use the cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/cluster-starts.png&#34; alt=&#34;cluster starts&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这样，就在你的电脑上启动了一个单节点的 Kubernetes 集群。Minikube 会给你配置 &lt;code&gt;kubectl&lt;/code&gt;，所以你现在就可以运行容器了。&lt;/p&gt;

&lt;p&gt;Minikube 创建了一个 Host-Only 的网络界面来和 Node 通信。要和其中的 Pod 或者 Service 通信，就需要跟这个地址进行交互。可以使用 &lt;code&gt;minikube ip&lt;/code&gt; 命令：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/minikube-ip.png&#34; alt=&#34;minikube ip&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Minikube 还自带了 Kubernetes Dashboard。要浏览这个界面，可以使用内置的 &lt;code&gt;minikube dashboard&lt;/code&gt;命令。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/dashboard-cmd.png&#34; alt=&#34;dashboard command&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/dashboard.png&#34; alt=&#34;dashboard screen shot&#34; /&gt;&lt;/p&gt;

&lt;p&gt;一般来说，Minikube 支持 Kubernetes 集群的所有内容。你可以使用 &lt;code&gt;kubectl exec&lt;/code&gt; 来在 pod 内获取一个 bash shell。还可以使用 &lt;code&gt;kubectl port-forward&lt;/code&gt; 以及 &lt;code&gt;kubectl proxy&lt;/code&gt; 命令来把通信从本地转发到一个 pod 或者 API server。&lt;/p&gt;

&lt;p&gt;Minikube 是运行在本地而不是云供应商，因此特定的 LoadBalancer 以及 PersistentVolume 也就无法使用了；当然，还是可以使用 NodePort 以及 HostPath 作为替代方案。&lt;/p&gt;

&lt;h2 id=&#34;架构&#34;&gt;架构&lt;/h2&gt;

&lt;p&gt;Minikube 构建于 Docker 的 &lt;a href=&#34;https://github.com/docker/machine/tree/master/libmachine&#34; target=&#34;_blank&#34;&gt;libmachine&lt;/a&gt;，利用他的驱动模型来创建、管理本地运行的虚拟机，并与其交互。&lt;/p&gt;

&lt;p&gt;慷慨的 &lt;a href=&#34;https://redspread.com/&#34; target=&#34;_blank&#34;&gt;RedSpread&lt;/a&gt;向 Minikube 贡献了他们的 &lt;a href=&#34;https://github.com/redspread/localkube&#34; target=&#34;_blank&#34;&gt;localkube&lt;/a&gt; 代码，得到这一帮助，我们有了在虚拟机中创建单进程 Kubernetes 集群的能力。Localkube 把 etcd、DNS、Kubelet 以及所有的 Kubernetes Master 组件都集成到了一个单独的 Go Lib 中并使用不同的 goroutine 执行。&lt;/p&gt;

&lt;h2 id=&#34;后续发展&#34;&gt;后续发展&lt;/h2&gt;

&lt;p&gt;Minikube 现在已经颇具可玩性了，为了改善 Kubernetes 开发体验，我们会持续对 Minikube 进行增强。如果你有任何设想，请移步 &lt;a href=&#34;https://github.com/kubernetes/minikube/issues&#34; target=&#34;_blank&#34;&gt;issue tracker&lt;/a&gt; 畅所欲言。&lt;/p&gt;

&lt;p&gt;我们希望尽快把下面列表中的内容加入 Minikube：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;为 OSX 以及 Windows 提供本地的 Native hypervisor 支持。

&lt;ul&gt;
&lt;li&gt;我们计划去掉对 Virtualbox 的依赖，转向 OSX 和 Windows 的本地 Hypervisor。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;增强对 Kubernetes 特性的支持

&lt;ul&gt;
&lt;li&gt;希望能够进一步缩小功能差异，以便于引入 Ingress 之类的内容。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;可配置的 Kubernetes 版本

&lt;ul&gt;
&lt;li&gt;目前 Minikube 只支持 Kubernetes 1.3。我们计划提供用户可控的 Kubernetes 版本支持，这样就能够让开发与生产环境更加贴近。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;社区&#34;&gt;社区&lt;/h2&gt;

&lt;p&gt;我们乐于听取 Minikube 的反馈，可以用下面的方式参与社区：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 &lt;a href=&#34;https://github.com/kubernetes/minikube&#34; target=&#34;_blank&#34;&gt;GitHub&lt;/a&gt; 中提出 Issue 或者提出功能要求。&lt;/li&gt;
&lt;li&gt;加入 &lt;a href=&#34;https://kubernetes.slack.com/&#34; target=&#34;_blank&#34;&gt;Slack&lt;/a&gt; 的 &lt;code&gt;#minikube&lt;/code&gt; 频道&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;请给 Minikube 一个机会，并让我们能够获知 Minikube 的运行情况。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中使用 Gluster FS</title>
      <link>/post/glusterfs-in-kubernetes/</link>
      <pubDate>Wed, 01 Jun 2016 22:57:51 +0800</pubDate>
      <guid>/post/glusterfs-in-kubernetes/</guid>
      <description>

&lt;p&gt;以 RC 形式运行在 Kubernetes 集群中的 Pod，会因为 Scale 等需要在不同的 Node 之间发生迁移，因此需要有独立于 Node 文件系统的共享存储服务，同时这一存储服务也应该符合集群的运行需要，简单的 NFS 不管是效率上还是可靠性上，都是不具备这一能力的。这里以 &lt;a href=&#34;https://www.gluster.org/&#34; target=&#34;_blank&#34;&gt;Gluster FS&lt;/a&gt;  作为存储引擎，为容器集群提供云存储服务。&lt;/p&gt;

&lt;p&gt;K8S 的存储卷使用稍有点古怪，Gluster FS 的使用，需要首先定义一个 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/services/&#34; target=&#34;_blank&#34;&gt;Endpoint + Service&lt;/a&gt; 形式的代理，来定义 Gluster FS 集群，然后就可以通过&lt;a href=&#34;http://blog.fleeto.us/translation/persistent-volumes&#34; target=&#34;_blank&#34;&gt;持久卷&lt;/a&gt;或者用 Pod 直接加载了。&lt;/p&gt;

&lt;h2 id=&#34;定义-service&#34;&gt;定义 Service&lt;/h2&gt;

&lt;p&gt;首先用一个 YML 文件来定义 Endpoint 和 Service：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
kind: List
apiVersion: v1
items:
- kind: Endpoints
  apiVersion: v1
  metadata:
    name: service_name
  subsets:
  - addresses:
    - ip: 12.34.56.78
    ports:
    - port: 111
- kind: Service
  apiVersion: v1
  metadata:
    name: service_name
  spec:
    ports:
      - port: 111
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Port 可随意填写&lt;/li&gt;
&lt;li&gt;Service Name 需要一致，这个值将会用到后面的引用中&lt;/li&gt;
&lt;li&gt;ip：Gluster FS 的 IP 地址&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;定义文件生成结束后，利用 &lt;code&gt;kubectl create -f xx.yaml&lt;/code&gt; 的方式加载到集群之中。可以用 &lt;code&gt;kubectl get svc,endpoints&lt;/code&gt; 来验证结果。&lt;/p&gt;

&lt;p&gt;接下来有两种加载方式可以选择：持久卷和 Pod 直接加载。&lt;/p&gt;

&lt;h2 id=&#34;pod-直接加载&#34;&gt;Pod 直接加载&lt;/h2&gt;

&lt;p&gt;可以在 Pod 中直接定义一个 Gluster FS 格式的卷来进行加载：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;spec:
containers:
- name: nginx-docker-images
  image: nginx:latest
  volumeMounts:
    - mountPath: /glusterfs
      name: test-volume
volumes:
  - name: test-volume
    glusterfs:
      endpoints: glusterfs-cluster
      path: gv0
      readOnly: false
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;endpoints: 这里指定的就是上一届中定义的服务名称&lt;/li&gt;
&lt;li&gt;path: gluster fs 中的卷名称&lt;/li&gt;
&lt;li&gt;readOnly: 是否只读加载&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;持久卷加载&#34;&gt;持久卷加载&lt;/h2&gt;

&lt;p&gt;首先定义一个持久卷：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolume
apiVersion: v1
metadata:
  name: gluster-volumen-gv01
spec:
  capacity:
    storage: 1Mi
  accessModes:
    - ReadWriteMany
  glusterfs:
    endpoints: glusterfs-svc
    path: gv0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;内容同上面的 Pod 卷定义大同小异，具体参数可以参考持久卷的相关文档。&lt;/p&gt;

&lt;p&gt;然后定义一个 PVC&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim2m
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Mi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后，在 Pod 中利用 PVC 来进行卷加载&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;volumes:
  - name: test-volume
    persistentVolumeClaim:
      # 上面定义的 PVC 名称
      claimName: myclaim1m
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的 Persistent Volumes</title>
      <link>/post/pv-in-kubernetes/</link>
      <pubDate>Wed, 01 Jun 2016 09:30:18 +0800</pubDate>
      <guid>/post/pv-in-kubernetes/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;经过一番实验，证明，这东西除了抽象，没啥鸟用，直接挂 Volume 应该是目前最佳选择。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&#34;持久卷-persistentvolumes&#34;&gt;持久卷 PersistentVolumes&lt;/h1&gt;

&lt;p&gt;本文描述了 Kubernetes 中的 &lt;strong&gt;PersistentVolumes&lt;/strong&gt;。要求读者有对卷 (volumes) 所有了解。&lt;/p&gt;

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;存储管理跟计算管理是两个不同的问题。&lt;strong&gt;PersistentVolume&lt;/strong&gt; 子系统，对存储的供应和使用做了抽象，以 API 形式提供给管理员和用户使用。要完成这一任务，我们引入了两个新的 API 资源：&lt;strong&gt;PersistentVolume（持久卷）&lt;/strong&gt; 和 &lt;strong&gt;PersistentVolumeClaim（持久卷申请）&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;PersistentVolume（PV）是集群之中的一块网络存储。跟 Node 一样，也是集群的资源。PV 跟 Volume (卷) 类似，不过会有独立于 Pod 的生命周期。这一 API 对象包含了存储的实现细节，例如 NFS、iSCSI 或者其他的云提供商的存储系统。&lt;/p&gt;

&lt;p&gt;PersistentVolumeClaim (PVC) 是用户的一个请求。他跟 Pod 类似。Pod 消费 Node 的资源，PVCs 消费 PV 的资源。Pod 能够申请特定的资源（CPU 和 内存）；Claim 能够请求特定的尺寸和访问模式（例如可以加载一个读写，以及多个只读实例）&lt;/p&gt;

&lt;h2 id=&#34;pv-和-pvc-的生命周期&#34;&gt;PV 和 PVC 的生命周期&lt;/h2&gt;

&lt;p&gt;PV 是集群的资源。PVC 是对这一资源的请求，也是对资源的所有权的检验。PV 和 PVC 之间的互动遵循如下的生命周期。&lt;/p&gt;

&lt;h3 id=&#34;供应&#34;&gt;供应&lt;/h3&gt;

&lt;p&gt;集群管理员会创建一系列的 PV。这些 PV 包含了为集群用户提供的真实存储资源。他们可利用 Kubernetes API 来消费。&lt;/p&gt;

&lt;h3 id=&#34;绑定&#34;&gt;绑定&lt;/h3&gt;

&lt;p&gt;用户创建一个包含了容量和访问模式的持久卷申请。Master 会监听 PVC 的产生，并尝试根据请求内容查找匹配的 PV，并把 PV 和 PVC 进行绑定。用户能够获取满足需要的资源，并且在使用过程中可能超出请求数量。&lt;/p&gt;

&lt;p&gt;如果找不到合适的卷，这一申请就会持续处于非绑定状态，一直到出现合适的 PV。例如一个集群准备了很多的 50G 大小的持久卷，（虽然总量足够）也是无法响应 100G 的申请的，除非把 100G 的 PV 加入集群。&lt;/p&gt;

&lt;h3 id=&#34;使用&#34;&gt;使用&lt;/h3&gt;

&lt;p&gt;Pod 把申请作为卷来使用。集群会通过 PVC 查找绑定的 PV，并 Mount 给 Pod。对于支持多种访问方式的卷，用户在使用 PVC 作为卷的时候，可以指定需要的访问方式。&lt;/p&gt;

&lt;p&gt;一旦用户拥有了一个已经绑定的 PVC，被绑定的 PV 就归该用户所有了。用户的 Pods 能够通过在 Pod 的卷中包含的 PVC 来访问他们占有的 PV。&lt;/p&gt;

&lt;h3 id=&#34;释放&#34;&gt;释放&lt;/h3&gt;

&lt;p&gt;当用户完成对卷的使用时，就可以利用 API 删除 PVC 对象了，而且他还可以重新申请。删除 PVC 后，对应的卷被视为 “被释放”，但是这时还不能给其他的 PVC 使用。之前的 PVC 数据还保存在卷中，要根据策略来进行后续处理。&lt;/p&gt;

&lt;h3 id=&#34;回收&#34;&gt;回收&lt;/h3&gt;

&lt;p&gt;PV 的回收策略向集群阐述了在 PVC 释放卷的时候，应如何进行后续工作。目前可以采用三种策略：保留，回收或者删除。保留策略允许重新申请这一资源。在持久卷能够支持的情况下，删除策略会同时删除持久卷以及 AWS EBS/GCE PD 或者 Cinder 卷中的存储内容。如果插件能够支持，回收策略会执行基础的擦除操作（&lt;code&gt;rm -rf /thevolume/*&lt;/code&gt;），这一卷就能被重新申请了。&lt;/p&gt;

&lt;h2 id=&#34;持久卷的类型&#34;&gt;持久卷的类型&lt;/h2&gt;

&lt;p&gt;持久卷是以插件方式实现的，目前支持如下插件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GCEPersistentDisk&lt;/li&gt;
&lt;li&gt;AWSElasticBlockStore&lt;/li&gt;
&lt;li&gt;NFS&lt;/li&gt;
&lt;li&gt;iSCSI&lt;/li&gt;
&lt;li&gt;RBD (Ceph Block Device)&lt;/li&gt;
&lt;li&gt;Glusterfs&lt;/li&gt;
&lt;li&gt;HostPath (单节点测试使用)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;持久卷&#34;&gt;持久卷&lt;/h2&gt;

&lt;p&gt;每个 PV 包含一个 spec 以及 status ，用于描述该卷的规格和状态。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  apiVersion: v1
  kind: PersistentVolume
  metadata:
    name: pv0003
  spec:
    capacity:
      storage: 5Gi
    accessModes:
      - ReadWriteOnce
    persistentVolumeReclaimPolicy: Recycle
    nfs:
      path: /tmp
      server: 172.17.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;capacity-容量&#34;&gt;Capacity（容量）&lt;/h3&gt;

&lt;p&gt;一般来说，PV 会指定存储容量。这里需要使用 PV 的 &lt;strong&gt;capcity&lt;/strong&gt; 属性。参见 Kubernetes 的 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/design/resources.md&#34; target=&#34;_blank&#34;&gt;Resource Model&lt;/a&gt; 一文，来获取这一属性的计量单位 (Mi/Gi&amp;hellip;.)。&lt;/p&gt;

&lt;p&gt;目前存储大小是唯一一个能够被申请的指标，今后会加入更多属性，例如 IOPS，吞吐能力等。&lt;/p&gt;

&lt;h3 id=&#34;access-modes-访问模式&#34;&gt;Access Modes（访问模式）&lt;/h3&gt;

&lt;p&gt;只要资源提供者支持，持久卷能够被用任何方式加载到主机上。每种存储都会有不同的能力，每个 PV 的访问模式也会被设置成为该卷所支持的特定模式。例如 NFS 能够支持多个读写客户端，但是某个 NFS PV 可能会在服务器上以只读方式使用。每个 PV 都有自己的一系列的访问模式，这些访问模式取决于 PV 的能力。&lt;/p&gt;

&lt;p&gt;访问模式的可选范围如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ReadWriteOnce&lt;/strong&gt;：该卷能够以读写模式被加载到一个节点上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ReadOnlyMany&lt;/strong&gt;：该卷能够以只读模式加载到多个节点上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ReadWriteMany&lt;/strong&gt;：该卷能够以读写模式被多个节点同时加载。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在 CLI 下，访问模式缩写为：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RWO&lt;/strong&gt;：ReadWriteOnce&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ROX&lt;/strong&gt;：ReadOnlyMany&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RWX&lt;/strong&gt;：ReadWriteMany&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;重要！一个卷不论支持多少种访问模式，同时只能以一种访问模式加载。例如一个 GCEPersistentDisk 既能支持 ReadWriteOnce ，也能支持 ReadOnlyMany。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;recycling-policy-回收策略&#34;&gt;Recycling Policy（回收策略）&lt;/h3&gt;

&lt;p&gt;当前的回收策略可选值包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retain - 人工重新申请&lt;/li&gt;
&lt;li&gt;Recycle - 基础擦除（“&lt;code&gt;rm -rf /thevolume/*&lt;/code&gt;”）&lt;/li&gt;
&lt;li&gt;Delete - 相关的存储资产例如 AWS EBS，GCE PD 或者 OpenStack Cinder 卷一并删除。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;目前，只有 NFS 和 HostPath 支持 Recycle 策略，AWS EBS、GCE PD 以及 Cinder 卷支持 Delete 策略（*其他的都是 Retain 是吧。。*）。&lt;/p&gt;

&lt;h3 id=&#34;阶段-phase&#34;&gt;阶段（Phase）&lt;/h3&gt;

&lt;p&gt;一个卷会处于如下阶段之一：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Available&lt;/strong&gt;：可用资源，尚未被绑定到 PVC 上&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bound&lt;/strong&gt;：该卷已经被绑定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Released&lt;/strong&gt;：PVC 已经被删除，但该资源尚未被集群回收&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failed&lt;/strong&gt;：该卷的自动回收过程失败。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CLI 会显示绑定到该 PV 的 PVC。&lt;/p&gt;

&lt;h2 id=&#34;persistentvolumeclaims-持久卷申请&#34;&gt;PersistentVolumeClaims（持久卷申请）&lt;/h2&gt;

&lt;p&gt;每个 PVC 包含一个 spec 以及 status，用以表达其规格和状态。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;访问模式&#34;&gt;访问模式&lt;/h3&gt;

&lt;p&gt;PVC 使用跟 PV 一致的访问模式。&lt;/p&gt;

&lt;h3 id=&#34;资源&#34;&gt;资源&lt;/h3&gt;

&lt;p&gt;PVC 跟 Pod 一样可以请求特定数量的资源。在这里的请求内容就是存储（storage）。&lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/design/resources.md&#34; target=&#34;_blank&#34;&gt;Resource Model&lt;/a&gt; 文中提到的内容对 PV 和 PVC 同样适用。&lt;/p&gt;

&lt;h2 id=&#34;pvc-卷&#34;&gt;PVC 卷&lt;/h2&gt;

&lt;p&gt;Pod 能够借助 PVC 来访问存储。PVC 必须跟 Pod 处于同一个命名空间。集群找到 Pod 命名空间中的 PVC，然后利用 PVC 获取到背后的 PV。这个卷就会被加载到主机上，让 Pod 可以使用。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Pod
apiVersion: v1
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: dockerfile/nginx
      volumeMounts:
      - mountPath: &amp;quot;/var/www/html&amp;quot;
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>kubectl config 文件示例</title>
      <link>/post/kubeconfig-sample/</link>
      <pubDate>Wed, 18 May 2016 01:48:04 +0800</pubDate>
      <guid>/post/kubeconfig-sample/</guid>
      <description>&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Config
# Clusters 一节用于存储对多个 Master 的定义，大致包含证书，服务器地址等信息
clusters:
- cluster:
    certificate-authority: certs/pd/ca.crt
    server: https://10.211.55.87:6443
  name: pd-secure
# Users 一节用于存储用户凭据，可对应 Token, Basic 等验证方法
users:
- name: fake-user
  user:
    password: password
    username: admin  
# Contexts 这里会用上面的集群和用户信息，来形成完整的连接信息
contexts:
- context:
    cluster: pd-secure
    namespace: kube-system
    user: fake-user
  name: pd-local
# 当前生效的 Context，可以利用 kubectl config use-context 来切换
current-context: pd-local
preferences: {}
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>在 Kubernetes 1.2 中使用部署（Deployment）对象</title>
      <link>/post/using-deployment-objects-with/</link>
      <pubDate>Thu, 07 Apr 2016 05:43:14 +0800</pubDate>
      <guid>/post/using-deployment-objects-with/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/04/using-deployment-objects-with&#34; target=&#34;_blank&#34;&gt;Using Deployment objects with Kubernetes 1.2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 简化了应用的部署管理，大多数操作都可以用一个 API 或者命令行搞定，例如应用生成、金丝雀测试以及升级。我们为什么还需要部署对象？&lt;/p&gt;

&lt;p&gt;部署对象把部署和滚动更新进行了自动化。跟 &lt;code&gt;kubectl&lt;/code&gt; 的 &lt;code&gt;rolling-update&lt;/code&gt; 相比，部署 API 更加清晰快速，在服务端实现，有更多的功能（例如即使滚动更新已经完成，你还是可以回滚到之前的版本）。&lt;/p&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;img src=&#34;images/image03.gif&#34; alt=&#34;rolling update&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;预备&#34;&gt;预备&lt;/h2&gt;

&lt;p&gt;要完成本文的内容，需要三个条件：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一个运行的 Kuberntes 集群：&lt;/strong&gt;：如果还没有，参考 &lt;a href=&#34;http://kubernetes.io/docs/getting-started-guides/&#34; target=&#34;_blank&#34;&gt;Getting Started Guides&lt;/a&gt;，其中包含针对笔记本电脑、云提供商以及物理服务器的各种方案。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubectl，Kubernetes 客户端&lt;/strong&gt;：如果运行 &lt;code&gt;kubectl cluster-info&lt;/code&gt; 之后看到一个 URL，那么就可以了。否则的话，需要&lt;a href=&#34;http://kubernetes.io/docs/user-guide/prereqs/&#34; target=&#34;_blank&#34;&gt;安装和配置 kubectl&lt;/a&gt;；如果使用的 Google 容器云，需要参考 &lt;a href=&#34;https://cloud.google.com/container-engine/docs/before-you-begin&#34; target=&#34;_blank&#34;&gt;Instrctions for hosted solutions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes/kubernetes.github.io/tree/master/docs/user-guide/update-demo&#34; target=&#34;_blank&#34;&gt;配置文件&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果你懒得自己做，可以&lt;a href=&#34;https://youtu.be/eigalYy0v4w&#34; target=&#34;_blank&#34;&gt;观看视频&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;运行&#34;&gt;运行&lt;/h2&gt;

&lt;p&gt;配置文件中包含一个静态网站，我们希望用它提供服务。从 Kubernetes 仓库的根，执行&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl proxy --www=docs/user-guide/update-demo/local/ &amp;amp;

Starting to serve on …
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这会在端口 8001 运行一个代理服务器。现在可以由 &lt;a href=&#34;http://localhost:8001&#34; target=&#34;_blank&#34;&gt;http://localhost:8001&lt;/a&gt; 来访问示例网站（目前还是个空白页面），接下来我们想要运行一个 App，并在网站上显示：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl run update-demo
--image=gcr.io/google_containers/update-demo:nautilus --port=80 -l  name=update-demo
deployment “update-demo” created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这一操作会部署一个单实例的应用，应用的镜像是 “update-demo:nautilus”，你可以在 &lt;a href=&#34;http://localhost:8001/static/&#34; target=&#34;_blank&#34;&gt;http://localhost:8001/static/&lt;/a&gt; 看到结果：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;kubectl run&lt;/code&gt; 会输出被创建资源的类型和名字。在 1.2 中变成了创建一个部署资源。后续操作中可以利用这一部署资源，例如 &lt;code&gt;kubectl get deployment&lt;/code&gt; 或者 &lt;code&gt;kubectl explose deployment&lt;/code&gt;。如果你希望编写一个向前兼容的自动化脚本，可以使用 &lt;code&gt;-o name&lt;/code&gt; 来运行 &lt;code&gt;kubectl run&lt;/code&gt; 命令，这样就会生成一个简短的输出 &amp;ldquo;deployment/&amp;ldquo;，可以用于后续命令。&lt;code&gt;--generator&lt;/code&gt; 标记也能用于 &lt;code&gt;kubernetes run&lt;/code&gt; 命令来生成其他类型的资源，例如设置为 &amp;ldquo;run/v1&amp;rdquo;，可以生成一个 Replication Controller，这是 1.1 和 1.0 的缺省行为，如果设置为 &amp;ldquo;run-pod/v1&amp;rdquo;，会创建一个 Pod，例如 &lt;code&gt;--restart=Never&lt;/code&gt; 的 Pod。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&#34;images/deployment-API-5.png&#34; alt=&#34;boot up&#34; /&gt;&lt;/p&gt;

&lt;p&gt;上面的卡片显示了 Kuberntes 的 Pod，包含 Pod 的名字（ID）、状态、镜像以及标签。&lt;/p&gt;

&lt;h2 id=&#34;扩展&#34;&gt;扩展&lt;/h2&gt;

&lt;p&gt;现在我们来把这个应用集群扩大一点。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl scale deployment/update-demo --replicas=4
deployment &amp;quot;update-demo&amp;quot; scaled
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/deployment-API-2.png&#34; alt=&#34;scale&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;更新&#34;&gt;更新&lt;/h2&gt;

&lt;p&gt;更新一下应用：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl edit deployment/update-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;他会打开你的缺省编辑器，你可以立即更新这一部署。查找 &lt;code&gt;.spec.template.spec.containers[0].image&lt;/code&gt;，把 &amp;ldquo;nautilus&amp;rdquo; 换成 &amp;ldquo;kitty&amp;rdquo;，保存文件，你会看到：&lt;code&gt;deployment &amp;quot;update-demo&amp;quot; edited&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;现在你把应用的镜像从 &amp;ldquo;update-demo:nautilus&amp;rdquo; 换成了 &amp;ldquo;update-demo:kitty&amp;rdquo;。部署对象允许在不停机的情况下进行渐进式的更新。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/deployment-API-3.png&#34; alt=&#34;update&#34; /&gt;&lt;/p&gt;

&lt;p&gt;等待一段时间，就会发现，更新好像卡住了。&lt;/p&gt;

&lt;h2 id=&#34;排错&#34;&gt;排错&lt;/h2&gt;

&lt;p&gt;如果仔细看看，你会发现使用 &amp;ldquo;kitty&amp;rdquo; 镜像的 Pod 在持续的 Pending 状态。因为这种失败，部署过程自动停止。我们可以观察一下新的 Pod，看看发生了什么问题：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl describe pod/update-demo-1326485872-a4key
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看 Pod 的事件，会看到 Kubernetes 无法获取这个不存在的镜像。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Failed to pull image &amp;quot;gcr.io/google_containers/update-demo:kitty&amp;quot;: Tag kitty not found in repository gcr.io/google_containers/update-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;回滚&#34;&gt;回滚&lt;/h2&gt;

&lt;p&gt;那么我们就要撤销之前的更新，花时间来找找我们需要的镜像名称。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl rollout undo deployment/update-demo deployment &amp;quot;update-demo&amp;quot; rolled back
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/deployment-API-2.png&#34; alt=&#34;roll back&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这样就一切恢复如初了。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://kubernetes.io/docs/user-guide/deployments/#rolling-back-a-deployment&#34; target=&#34;_blank&#34;&gt;rolling back a Deployment&lt;/a&gt; 一文，介绍了更多的回滚有关的内容。&lt;/p&gt;

&lt;h2 id=&#34;再更新&#34;&gt;再更新&lt;/h2&gt;

&lt;p&gt;过一会，我们知道我们应该使用的镜像是 “kitten”，现在把 &lt;code&gt;.spec.template.spec.containers[0].image&lt;/code&gt; 标记从 &amp;ldquo;nautilus&amp;rdquo; 替换为 &amp;ldquo;kitten&amp;rdquo;。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl edit deployment/update-demo
deployment &amp;quot;update-demo&amp;quot; edited
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/deployment-API-1.png&#34; alt=&#34;update2&#34; /&gt;&lt;/p&gt;

&lt;p&gt;现在看到，4 只小猫都在运行了，这意味着我们成功的更新了应用，幕后细节可以从下面的命令获知：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl describe deployment/update-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;images/deployment-API-6.png&#34; alt=&#34;describe deployment&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这里可以看到，部署对象管理着另外一个名为 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/replicasets/&#34; target=&#34;_blank&#34;&gt;Replica Set&lt;/a&gt; 的资源，控制着 Pod 的实例数量。部署对象具有利用扩展和收缩来对 Replica Set 进行渐进升级的能力。&lt;/p&gt;

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

&lt;p&gt;现在我们讲了部署对象的一些基础：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;利用部署对象和 &lt;code&gt;kubectl run&lt;/code&gt; 来部署应用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl edit&lt;/code&gt; 命令更新部署对象，从而更新应用。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;kubectl rollout undo&lt;/code&gt; 来回滚到之前的部署。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：Kubernetes 1.2 中，部署对象（Beta 版本）具有完整的功能，是缺省启用的。如果之前用过了 Kubernetes 1.1 中的部署对象，因为这一次升级是向后不兼容的，所以在使用 1.2 之前，请&lt;strong&gt;删除所有的 1.1 的部署资源&lt;/strong&gt;（包括 Replication Controller 以及下属的 Pod）。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>在 Kubernetes 1.2 中使用 Spark 和 Zeppelin 处理大数据</title>
      <link>/post/spark-zeppelin-in-kubernetes-1.2/</link>
      <pubDate>Thu, 07 Apr 2016 05:41:13 +0800</pubDate>
      <guid>/post/spark-zeppelin-in-kubernetes-1.2/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/03/using-spark-and-zeppelin-to-process-big-data-on-kubernetes&#34; target=&#34;_blank&#34;&gt;Using Spark and Zeppelin to process big data on Kubernetes 1.2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;大数据应用与日俱增，很多 Kubernetes 用户希望利用 Kubernetes 集群来运行 &lt;a href=&#34;http://spark.apache.org/&#34; target=&#34;_blank&#34;&gt;Apache Spark&lt;/a&gt;，借助容器的能力来获得弹性和移植性。在 Kubernetes 1.2 中，为 Spark、Zeppelin 以及其他应用提供了协同工作的平台。&lt;/p&gt;

&lt;h2 id=&#34;zeppelin-是什么&#34;&gt;Zeppelin 是什么？&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://zeppelin.incubator.apache.org/&#34; target=&#34;_blank&#34;&gt;Apache Zeppelin&lt;/a&gt; 是一个基于 Web 的交互式数据分析工具。Spark 也是 Zeppelin 所支持的后台之一。在命令行交互以及 Scala 之外，Zeppelin 为用户提供了一种较为简易的和 Spark 集群交互的能力。&lt;/p&gt;

&lt;h2 id=&#34;为什么用-kubernetes&#34;&gt;为什么用 Kubernetes？&lt;/h2&gt;

&lt;p&gt;有很多无需 Kubernetes 运行 Spark 的方式：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;独立服务器来运行&lt;/li&gt;
&lt;li&gt;可以在 &lt;a href=&#34;https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/YARN.html&#34; target=&#34;_blank&#34;&gt;YARN&lt;/a&gt; 集群上，和 Hadoop 以及 HDFS 协同工作&lt;/li&gt;
&lt;li&gt;可以在 &lt;a href=&#34;http://mesos.apache.org/&#34; target=&#34;_blank&#34;&gt;Mesos&lt;/a&gt; 集群上跟其他 Mesos 应用一起运行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;那么为什么在 Kubernetes 上运行 Spark ？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一个单纯的、统一的集群接口：Kubernetes 能够支撑多种负载；无需为 YARN/HDFS 和其他应用进行不同的编排。&lt;/li&gt;
&lt;li&gt;提高服务器的利用率：在 Spark 和其他应用之间共享节点。你可以用一个串流应用来为 Spark 管线提供数据，也可以运行一个 Nginx Pod 来提供 Web 服务，无需对节点进行静态分区。&lt;/li&gt;
&lt;li&gt;不同负载之间的隔离：Kubernetes 的 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/proposals/resource-qos.md&#34; target=&#34;_blank&#34;&gt;服务质量 (Quality of Service)&lt;/a&gt; 机制让用户能够安全的同时调度批处理应用（类似 Spark）以及延迟敏感的其他应用。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;spark-加载&#34;&gt;Spark 加载&lt;/h2&gt;

&lt;p&gt;这里使用 &lt;a href=&#34;https://cloud.google.com/container-engine/&#34; target=&#34;_blank&#34;&gt;Google 容器引擎（GKE）&lt;/a&gt; 来进行演示，不过这些过程也适用于其他的 Kubernetes 集群。首先创建一个容器引擎的集群，并指定其 scope 为 &lt;code&gt;storage-full&lt;/code&gt;。这样的设置允许该集群写入到私有的 Google 云存储中（我们会在后面解释这一选择的理由）：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gcloud container clusters create spark --scopes storage-full --machine-type n1-standard-4&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意我们使用的类型是 &lt;code&gt;n1-standard-4&lt;/code&gt;（比缺省的节点类型稍大），用来演示 Pod 的纵向扩展能力。然而 Spark 在缺省的 &lt;code&gt;n1-standard-1&lt;/code&gt; 上也能够运行。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;集群建立之后，就可以利用 Kubernetes Github 仓库中的配置文件来在集群中启动 Spark 了：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone https://github.com/kubernetes/kubernetes.git&lt;/code&gt;
&lt;code&gt;kubectl create -f kubernetes/examples/spark&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubernetes/examples/spark&lt;/code&gt; 是一个目录，这一命令告诉 kubectl 创建这一目录下所有的 YAML 文件所定义的 Kubernetes 对象。&lt;/p&gt;

&lt;p&gt;Pod（尤其是 Apache Zeppelin）很大，所以需要一些时间让 Docker 来拉取镜像，一旦运行成功，你会看到类似以下的输出：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl get pods
NAME                            READY     STATUS    RESTARTS   AGE
spark-master-controller-v4v4y   1/1       Running   0          21h
spark-worker-controller-7phix   1/1       Running   0          21h
spark-worker-controller-hq9l9   1/1       Running   0          21h
spark-worker-controller-vwei5   1/1       Running   0          21h
zeppelin-controller-t1njl       1/1       Running   0          21h
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看出 Kubernetes 正在运行一个 Zeppelin 实例，一个 Spark Master 以及三个 Spark worker。&lt;/p&gt;

&lt;h2 id=&#34;设置-zeppelin-的安全代理&#34;&gt;设置 Zeppelin 的安全代理&lt;/h2&gt;

&lt;p&gt;接下来要设置一个从本机到 Zeppelin 的安全代理，以便从你的机器访问 Zeppelin 实例。（注意这里需要根据你集群中的 Zeppelin Pod 的实际名称修改这个命令）&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ kubectl port-forward zeppelin-controller-t1njl 8080:8080&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这就建立了一个从 Kubernetes 集群到 Pod ( zeppelin-controller-t1njl ) 的安全连接，并把这个端口映射到本地的 8080 端口，让你可以安全的使用 Zeppelin。&lt;/p&gt;

&lt;h2 id=&#34;对正在运行的-zeppelin-做点什么&#34;&gt;对正在运行的 Zeppelin 做点什么？&lt;/h2&gt;

&lt;p&gt;例如我们将要演示如何创建一个简单的电影推荐模型。这一演示基于 &lt;a href=&#34;http://spark.apache.org/docs/1.5.2/mllib-collaborative-filtering.html&#34; target=&#34;_blank&#34;&gt;Spark 网站提供的代码&lt;/a&gt;，为了演示 Kubernetes 的特性，做了一点修改。&lt;/p&gt;

&lt;p&gt;现在安全代理启动了，浏览 &lt;code&gt;http://localhost:8080/&lt;/code&gt; 应该会看到下面的页面：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/welcome_to_zeppelin.png&#34; alt=&#34;Welcom to zeppelin&#34; /&gt;&lt;/p&gt;

&lt;p&gt;点击 &amp;ldquo;Import note&amp;rdquo;，设置一个随便什么名字（比如 “Movies”），点击 “Add from URL”，网址输入：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://gist.githubusercontent.com/zmerlynn/875fed0f587d12b08ec9/raw/6
eac83e99caf712482a4937800b17bbd2e7b33c4/movies.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;然后点击 &amp;ldquo;Import Note&amp;rdquo;，就会获取到这个 Demo 所需的 Zeppelin note。现在我们就有了一个 Notebook，如果点击这个 Note，会看到下面的内容：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/zeppelin_note.png&#34; alt=&#34;zeppelin note&#34; /&gt;&lt;/p&gt;

&lt;p&gt;接下来可以点击右上角的 Play 按钮，就会创建一个新的电影推荐模型。在 Spark 应用模型中，Zeppelin 扮演 &lt;a href=&#34;https://spark.apache.org/docs/1.5.2/cluster-overview.html&#34; target=&#34;_blank&#34;&gt;Spark Driver Program&lt;/a&gt;，他负责同 Spark 集群交互，并令集群完成任务。在这里 Zeppelin Pod 中的应用获取数据，并发送给 Spark Master，Master 将其分发给 Worker，这些 Worker 会利用 Zeppelin 提供的代码来生成一个电影推荐模型。在下一节中，我们将会讲讲向 GCS 存储数据的问题。&lt;/p&gt;

&lt;h2 id=&#34;使用-google-云存储-可选&#34;&gt;使用 Google 云存储（可选）&lt;/h2&gt;

&lt;p&gt;在这个演示中，我们将使用 Google 云存储来保存我们的模型数据，这样就可以避免 Pod 生命周期的限制了。Kubernetes 版本的 Spark 内置了 &lt;a href=&#34;https://cloud.google.com/storage/&#34; target=&#34;_blank&#34;&gt;Google 云存储&lt;/a&gt; 的连接器。只要能够从运行着这个 Kubernetes 节点的 Google 容器引擎项目中能够访问到数据，那么同样的，你的 Spark 镜像也能利用 GCS 连接器访问数据。&lt;/p&gt;

&lt;p&gt;还可以在 Note 中修改例子中的变量，就可以保存和恢复电影推荐引擎的模型了，只要把这些变量指向 你有权访问的 GCS Bucket 就可以了。如果要创建一个 GCS Bucket，需要执行下面类似的命令：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gsutil mb gs://my-spark-models&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;URL 部分可以按照用户自己的需要来指定，执行后会生成相应的 Bucket 供应用来使用。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：运行这一模型，然后保存，这一过程远比运行后丢弃要慢。这很正常，但是如果要复用一个模型，那么计算后保存，复用时恢复就要比每次重新运算要快了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;使用-pod-的纵向扩展-可选&#34;&gt;使用 Pod 的纵向扩展（可选）&lt;/h3&gt;

&lt;p&gt;Spark 的 Works 有一些弹性功能，这让我们有机会：用 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/horizontal-pod-autoscaling/&#34; target=&#34;_blank&#34;&gt;Kubernetes Pod 纵向扩展能力&lt;/a&gt; 来对 Spark worker 池自动扩展，为 Work 设置一个目标 CPU 阈值以及最大最小规模即可。这样就不需手工配置 Worker 集群了。&lt;/p&gt;

&lt;p&gt;下面就是创建自动伸缩的指令&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：如果没有改变这一集群的机器类型，你可能需要把 &amp;ndash;max 参数设置的小一点。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;kubectl autoscale --min=1 --cpu-percent=80 --max=10 rc/spark-worker-controller&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;可以看到 Replication Controller 把实例数降低到 1，证明了自动伸缩的效果。使用 &lt;code&gt;bubectl get rc&lt;/code&gt;，会看到 spark-worker-controller 的 “replicas” 这一列会落回到 1。&lt;/p&gt;

&lt;p&gt;之前我们运行的负载太低。为了让任务运行更久一点，我们把 &amp;ldquo;rank = 100&amp;rdquo; 改成 &amp;ldquo;rank = 200&amp;rdquo;。这样在点击 &amp;ldquo;Play&amp;rdquo; 按钮后，Spark worker 会迅速增加到 20 pod，在工作完成后，最多需要五分钟，Work 池开始回落到单一实例。&lt;/p&gt;

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

&lt;p&gt;本文中我们展示了如何在 Kubernetes 中运行 Spark 和 Zeppelin，以及如何使用 Google 云存储来保存 Spark Model，还介绍了利用 Kubernetes 自动伸缩特性来动态设置 Spark worker 池。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 1.2 中利用 Ingress 简化复杂网络</title>
      <link>/post/kubernetes-1.2-with-ingress/</link>
      <pubDate>Wed, 06 Apr 2016 07:50:05 +0800</pubDate>
      <guid>/post/kubernetes-1.2-with-ingress/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/03/kubernetes-1.2-and-simplifying-advanced-networking-with-ingress&#34; target=&#34;_blank&#34;&gt;Kubernetes 1.2 and simplifying advanced networking with Ingress&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在 Kubernetes 中，服务和 Pod 的 IP 地址缺省只能被集群网络路由。所有边缘路由器传来的流量要么被丢弃，要么被转向。在 Kubernetes 中，我们对 Ingress 对象做了改进，用于简化传入连接进入集群服务的过程。对其进行配置，能够给服务外部可达的 URL，负载均衡，Terminate SSL，提供具名虚拟主机等大量功能。&lt;/p&gt;

&lt;h2 id=&#34;ingress-controller&#34;&gt;Ingress Controller&lt;/h2&gt;

&lt;p&gt;如今，受容器或者虚拟机的影响，对 Web 服务器或者负载均衡的配置变得麻烦了。多数的 Web 服务器配置文件很像。虽说有些应用有些古怪，但是总的说来还是可以通过一些逻辑来达到目的的。在 Kuberntes 1.2 中，Ingress 实现了这些想法，Ingress 控制器就是用于处理这些特别的 Ingress “类” （可能是负载均衡的一个实例，或者复杂一些的提供 GSLB、CDN、DDoS 保护等功能的前端）中的这些个性的东西。Ingress Controller 是一个守护进程，以 Kuberntes Pod 的形式进行部署，他会监控 API 服务的 /ingress 终结点来获取对 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/ingress/&#34; target=&#34;_blank&#34;&gt;Ingress 资源&lt;/a&gt; 的更新。他的任务就是实现 Ingress 请求。&lt;/p&gt;

&lt;p&gt;为了运行下面的例子，你的 Kubternetes 集群必须仅有一个支持 TLS 的 Ingress 控制器。如果你的集群运行在云提供商环境中，首先查找一下 “kube-system” 命名空间，查找 Ingress 控制器的 RC。如果没有的话，需要部署一个 &lt;a href=&#34;https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx&#34; target=&#34;_blank&#34;&gt;nginx 控制器&lt;/a&gt; 或者 用不到 100 行代码&lt;a href=&#34;https://github.com/kubernetes/contrib/tree/master/ingress/controllers#writing-an-ingress-controller&#34; target=&#34;_blank&#34;&gt;自行实现&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;务必花费少许时间来了解一下现存控制器的限制（ GCE, nginx ）。&lt;/p&gt;

&lt;h2 id=&#34;tls-termination-和-http-负载均衡&#34;&gt;TLS termination 和 HTTP 负载均衡&lt;/h2&gt;

&lt;p&gt;Ingress 用于承接服务，所以很适合做负载均衡以及中心化的安全配置。如果你熟悉 Go 语言，Ingress 在集群中扮演了 &lt;a href=&#34;https://golang.org/pkg/net/http/#Server&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;net/http’s “Server”&lt;/code&gt;&lt;/a&gt; 的角色。下面的例子示范了如何配置 TLS termination。负载均衡是 Ingress 的必选项目，所以只要创建了这一对象，就有了负载均衡能力。&lt;/p&gt;

&lt;p&gt;首先创建一个测试服务。我们会运行一个简单的 Echo 服务器，用来告知我们正在运行的内容（&lt;a href=&#34;https://github.com/kubernetes/contrib/tree/master/ingress/echoheaders&#34; target=&#34;_blank&#34;&gt;源代码&lt;/a&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl run echoheaders 
--image=gcr.io/google_containers/echoserver:1.3 --port=8080
$ kubectl expose deployment echoheaders --target-port=8080 
--type=NodePort
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果你是在云服务提供商提供的集群上运行，确认你可以由外网通过 Nodeport 访问到这一服务。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ NODE_IP=$(kubectl get node `kubectl get po -l run=echoheaders 
--template &#39;{{range .items}}{{.spec.nodeName}}{{end}}&#39;` --template
&#39;{{range $i, $n := .status.addresses}}{{if eq $n.type 
&amp;quot;ExternalIP&amp;quot;}}{{$n.address}}{{end}}{{end}}&#39;)
$ NODE_PORT=$(kubectl get svc echoheaders --template &#39;{{range $i, $e 
:= .spec.ports}}{{$e.nodePort}}{{end}}&#39;)
$ curl $NODE_IP:$NODE_PORT
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这是一个简单的检查，如果最后一步失败了，可能需要设置一下&lt;a href=&#34;https://github.com/kubernetes/contrib/blob/master/ingress/controllers/gce/BETA_LIMITATIONS.md#creating-the-firewall-rule-for-glbc-health-checks&#34; target=&#34;_blank&#34;&gt;防火墙规则&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;接下来创建我们的 TLS secret：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout 
/tmp/tls.key -out /tmp/tls.crt -subj &amp;quot;/CN=echoheaders/O=echoheaders&amp;quot;
$ echo &amp;quot;
apiVersion: v1
kind: Secret
metadata:
name: tls
data:
tls.crt: `base64 -w 0 /tmp/tls.crt`
tls.key: `base64 -w 0 /tmp/tls.key`
&amp;quot; | kubectl create -f
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;配置 Ingress：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ echo &amp;quot;
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test
spec:
tls:
- secretName: tls
backend:
    serviceName: echoheaders
    servicePort: 8080
&amp;quot; | kubectl create -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样就得到了一个负载均衡 IP：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ kubectl get ing 
NAME      RULE      BACKEND            ADDRESS         AGE
test      -         echoheaders:8080   130.X.X.X      4m
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果等到 Ingress 控制器把你的后端设置为健康，会看到访问该 IP 80 端口的请求会被重定向到 443 端口，并会使用指定的 TLS 进行验证。&lt;/p&gt;

&lt;h2 id=&#34;未来&#34;&gt;未来&lt;/h2&gt;

&lt;p&gt;可以在 &lt;a href=&#34;http://kubernetes.io/docs/user-guide/ingress/&#34; target=&#34;_blank&#34;&gt;Ingress API&lt;/a&gt; 获得更多的信息。Ingress 还在 Beta 阶段，我们期待你的反馈。你也可以贡献控制器或者 API 代码。所有跟 &lt;a href=&#34;https://www.google.com/webhp?sourceid=chrome-instant&amp;amp;ion=1&amp;amp;espv=2&amp;amp;ie=UTF-8#q=ingress%20meaning&#34; target=&#34;_blank&#34;&gt;&amp;ldquo;Ingress&amp;rdquo;&lt;/a&gt; 的内容都可以，包含 DNS、不同的 TLS 模式、SNI、4 层负载均衡、内容缓存、更多的算法和健康检查等。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Pod 的一生</title>
      <link>/post/lifecycle-of-pod/</link>
      <pubDate>Wed, 02 Mar 2016 05:09:02 +0800</pubDate>
      <guid>/post/lifecycle-of-pod/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/&#34; target=&#34;_blank&#34;&gt;Pod Lifecycle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本文阐述了 Pod 的生命周期，这并不是一篇全面的文档，仅是这一话题的简介。&lt;/p&gt;

&lt;h1 id=&#34;pod-的阶段-phase&#34;&gt;Pod 的阶段（Phase）&lt;/h1&gt;

&lt;p&gt;按照《&lt;a href=&#34;http://kubernetes.io/v1.1/docs/user-guide/pod-states.html#restartpolicy&#34; target=&#34;_blank&#34;&gt;API 公约&lt;/a&gt;》 的描述，Pod 阶段是对生命周期的一个阶段的概括。他不是一个对 Pod 或者容器层次的状态的详尽结论，也不是一个全面的状态机。&lt;/p&gt;

&lt;p&gt;PodPhase 很封闭。除了本文提到的内容，不应该对一个 Pod 的 PodPhase 做出任何假设。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pending：Pod 被系统接收，但是其中的一个或者多个容器镜像尚未创建。这一过程包括下载镜像的时间，以及被计划运行之前的时间。&lt;/li&gt;
&lt;li&gt;Running：该 Pod 被分派给一个 Node，并且已经创建了所有容器。至少一个容器还在运行，或者正在被启动以及重启。&lt;/li&gt;
&lt;li&gt;Succeeded：Pod 中的所有容器都已经成功结束，并且不会重新启动。&lt;/li&gt;
&lt;li&gt;Failed：Pod 里所有的容器都已经结束运行，且至少一个容器的退出结果是失败的（被系统结束，或非 0 的退出状态）。&lt;/li&gt;
&lt;li&gt;Unknown：因为某些原因无法获得 Pod 状态，一般来说是 Pod 所处主机的通信出了故障。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&#34;pod-状态-condition&#34;&gt;Pod 状态（Condition）&lt;/h1&gt;

&lt;p&gt;Pod 中容器的就绪检测会报告就绪状况，其取值可能是 True、False 或者 Unknown。&lt;/p&gt;

&lt;h1 id=&#34;容器检测&#34;&gt;容器检测&lt;/h1&gt;

&lt;p&gt;Kubelet 会周期性的对容器进行&lt;a href=&#34;https://godoc.org/k8s.io/kubernetes/pkg/api/v1#Probe&#34; target=&#34;_blank&#34;&gt;检测&lt;/a&gt; 。这一过程是如下&lt;a href=&#34;https://godoc.org/k8s.io/kubernetes/pkg/api/v1#Handler&#34; target=&#34;_blank&#34;&gt;三种方法&lt;/a&gt;之一：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ExecAction：执行一个容器内的指定命令，如果命令返回码为 0 ，则代表成功。&lt;/li&gt;
&lt;li&gt;TCPSocketAction：对目标容器的 IP 地址执行 TCP 操作，如果指定端口开放，则代表成功。&lt;/li&gt;
&lt;li&gt;HTTPGetAction：对目标容器的 IP 地址、端口号以及路径进行 HTTP Get 操作，如果返回码在 200 和 400 之间，则检测成功。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;检测过程可能会有三种结果之一：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Success：容器成功通过检测。&lt;/li&gt;
&lt;li&gt;Failure：容器检测失败。&lt;/li&gt;
&lt;li&gt;Unknown：检测未能完成。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;目前 Kubelet 能够有两种独立的检测可以被触发：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LivenessProbe：用于检查当前容器是否存活，也就是正在运行。LivenessProbe 会告知 Kubelete 容器的健康状况。如果 LinenessProbe 失败，Kubelete 会杀掉这个容器，接下来，容器会根据 &lt;a href=&#34;http://kubernetes.io/v1.1/docs/user-guide/pod-states.html#restartpolicy&#34; target=&#34;_blank&#34;&gt;RestartPolicy（重启动策略）&lt;/a&gt;进行后续动作。容器在没进行这一检测之前的状态被标记为 Success。&lt;/li&gt;
&lt;li&gt;ReadinessProbe：当前容器是否已经就绪，可以对外提供服务。如果这一检测失败的话，即使这一Pod 还在运行，Endpoint 控制器也会把这个 Pod 的 IP 地址从相关服务中移除，这样他就不会从 Proxy 获取流量（例如容器在提供服务之前会有一个较长的启动时间，或者容器正在关机维护）。在初始化完成之前，缺省的就绪状态是 Failure。当没有进行检测的时候，容器的就绪状态缺省假设为 Success。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&#34;容器状态-statuses&#34;&gt;容器状态（Statuses）&lt;/h1&gt;

&lt;p&gt;对容器状态的详细信息可以参考&lt;a href=&#34;https://godoc.org/k8s.io/kubernetes/pkg/api/v1#PodStatus&#34; target=&#34;_blank&#34;&gt;ContainerStatuses&lt;/a&gt;。&lt;/p&gt;

&lt;h1 id=&#34;重启策略&#34;&gt;重启策略&lt;/h1&gt;

&lt;p&gt;RestartPolicy 有几个可能的取值：Always，OnFailure 以及 Never。他的缺省值是 Always。RestartPolicy 作用于一个 Pod 中的所有容器。RestartPolicy 适用于同一 Node 的 Kubelet 的重启动操作。失败的容器会被 Kubelet 重启，重启之前会有一个渐进的延迟，延迟时长是同步频率的 0、1、2、4、8&amp;hellip;倍，上限是五分钟，成功执行 10 分钟后会复位（延迟时间）。在 [Pod 文档中] 提到，Pod 一旦绑定到了一个 Node 上，就不会再绑定到其他 Node 了。这意味着即便是只有一个 Pod，也需要有控制器来进行操作，这样在 Node 失败的时候，才能保证 Pod 的存活。&lt;/p&gt;

&lt;p&gt;目前有三种可用的控制器：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Job：用来执行会结束的 Pod (例如批处理运算)。&lt;/li&gt;
&lt;li&gt;ReplicationController：不需要结束的 Pod (例如 Web Server)。&lt;/li&gt;
&lt;li&gt;DaemonSet：每台（物理）机只能运行一个的 Pod，这种 Pod 提供机器相关的系统服务。如果在 ReplicationController 或者 Daemon 之间举棋不定，可以参考 &lt;a href=&#34;http://kubernetes.io/v1.1/docs/admin/daemons.html#daemon-set-versus-replication-controller&#34; target=&#34;_blank&#34;&gt;Daemon Set vs Replication Controller&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ReplicationController 是唯一符合 RestartPolicy = Always 需要的。Job 就适合另外两种。&lt;/p&gt;

&lt;p&gt;所有三种控制器都有对应的 PodTemplate，跟 Pod 的字段一致。建议创建控制器，让控制器创建 Pod，而不是自行直接创建 Pod。这是因为 Pod 不具备适应服务器失败的能力，而控制器可以。&lt;/p&gt;

&lt;h1 id=&#34;pod-的生命期&#34;&gt;Pod 的生命期&lt;/h1&gt;

&lt;p&gt;一般来说， Pod 创建之后就不会消失，除非被手工销毁。销毁手段可能是人工、ReplicationController 或者其他控制器。唯一的例外是处于 Succeeded 或者 Failed 阶段一定时间的 Pod 会因过期（由 Master 决定）而被自动销毁。&lt;/p&gt;

&lt;p&gt;如果一个 Node 崩溃或者从集群断开，系统内的实体（目前称为 NodeController ）会负责执行策略（例如超时）并把丢失的 Node 中的所有 Pod 标记为 Failed。&lt;/p&gt;

&lt;h1 id=&#34;例子&#34;&gt;例子&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Pod 在 Running 状态，1 个容器，该容器成功退出

&lt;ul&gt;
&lt;li&gt;记录结束事件&lt;/li&gt;
&lt;li&gt;RestartPolicy 如果是&lt;/li&gt;
&lt;li&gt;Always：重启容器，Pod 保持 Running 状态&lt;/li&gt;
&lt;li&gt;OnFailure：Pod 转为 Succeeded 状态&lt;/li&gt;
&lt;li&gt;Never：Pod 转为 Succeeded 状态&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Pod 在 Running 状态，一个容器，容器失败退出

&lt;ul&gt;
&lt;li&gt;记录失败事件&lt;/li&gt;
&lt;li&gt;如果 RestartPolicy 是：&lt;/li&gt;
&lt;li&gt;Always：重启容器，Pod 保持 Running 状态&lt;/li&gt;
&lt;li&gt;OnFailure：重启容器，Pod 保持 Running 状态&lt;/li&gt;
&lt;li&gt;Never：Pod 进入 Failed 状态&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Pod 在 Running 状态，两个容器，容器 1 失败退出

&lt;ul&gt;
&lt;li&gt;记录失败事件&lt;/li&gt;
&lt;li&gt;如果 RestartPolicy 是：&lt;/li&gt;
&lt;li&gt;Always：重启容器， Pod 保持 Running 状态&lt;/li&gt;
&lt;li&gt;OnFailure：重启容器，Pod 保持 Running 状态&lt;/li&gt;
&lt;li&gt;Never：Pod 保持 Running 状态&lt;/li&gt;
&lt;li&gt;容器 2 退出&amp;hellip;&lt;/li&gt;
&lt;li&gt;记录失败事件&lt;/li&gt;
&lt;li&gt;如果 Restart Policy 是：

&lt;ul&gt;
&lt;li&gt;Always：重启容器，Pod 保持 Running 状态&lt;/li&gt;
&lt;li&gt;OnFailure：重启容器，Pod 保持 running 状态&lt;/li&gt;
&lt;li&gt;Never：Pod 进入 Failed 状态&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Pod 在 Running 状态，容器内存不足

&lt;ul&gt;
&lt;li&gt;容器失败退出&lt;/li&gt;
&lt;li&gt;记录 OOM 事件&lt;/li&gt;
&lt;li&gt;如果 RestartPolicy 是：&lt;/li&gt;
&lt;li&gt;Always：重启容器，Pod 保持 Running&lt;/li&gt;
&lt;li&gt;OnFailure：重启容器，Pod 保持 Running    - Never：记录失败事件，Pod 进入 Failed 状态&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Pod 在 Running 状态，一个磁盘坏掉。

&lt;ul&gt;
&lt;li&gt;所有容器被 Kill&lt;/li&gt;
&lt;li&gt;记录事件&lt;/li&gt;
&lt;li&gt;Pod 进入失败状态&lt;/li&gt;
&lt;li&gt;如果在控制器之下运行，则 Pod 会在其他位置被创建&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Pod 在 Running 状态，所在 Node 被断开&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NodeController 等待超时长&lt;/li&gt;
&lt;li&gt;NodeController 标记 Pod 为 Failed 状态&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如果在控制器之下运行，则 Pod 会在其他位置被创建&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;type PodStatus struct {
// Current condition of the pod.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/pod-states.md#pod-phase
Phase PodPhase `json:&amp;quot;phase,omitempty&amp;quot;`
// Current service state of pod.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/pod-states.md#pod-conditions
Conditions []PodCondition `json:&amp;quot;conditions,omitempty&amp;quot; patchStrategy:&amp;quot;merge&amp;quot; patchMergeKey:&amp;quot;type&amp;quot;`
// A human readable message indicating details about why the pod is in this condition.
Message string `json:&amp;quot;message,omitempty&amp;quot;`
// A brief CamelCase message indicating details about why the pod is in this state.
// e.g. &#39;OutOfDisk&#39;
Reason string `json:&amp;quot;reason,omitempty&amp;quot;`

// IP address of the host to which the pod is assigned. Empty if not yet scheduled.
HostIP string `json:&amp;quot;hostIP,omitempty&amp;quot;`
// IP address allocated to the pod. Routable at least within the cluster.
// Empty if not yet allocated.
PodIP string `json:&amp;quot;podIP,omitempty&amp;quot;`

// RFC 3339 date and time at which the object was acknowledged by the Kubelet.
// This is before the Kubelet pulled the container image(s) for the pod.
StartTime *unversioned.Time `json:&amp;quot;startTime,omitempty&amp;quot;`

// The list has one entry per container in the manifest. Each entry is currently the output
// of `docker inspect`.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/pod-states.md#container-statuses
ContainerStatuses []ContainerStatus `json:&amp;quot;containerStatuses,omitempty&amp;quot;`
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;type ContainerState struct {
// Details about a waiting container
Waiting *ContainerStateWaiting `json:&amp;quot;waiting,omitempty&amp;quot;`
// Details about a running container
Running *ContainerStateRunning `json:&amp;quot;running,omitempty&amp;quot;`
// Details about a terminated container
Terminated *ContainerStateTerminated `json:&amp;quot;terminated,omitempty&amp;quot;`
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>CentOS 7 &#43; Kubernetes 1.1.x  &#43; Docker 1.9.x  安装指南</title>
      <link>/post/install-kubernetes-and-docker-on-centos/</link>
      <pubDate>Thu, 24 Dec 2015 00:17:17 +0800</pubDate>
      <guid>/post/install-kubernetes-and-docker-on-centos/</guid>
      <description>

&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;http://docker.io/&#34; target=&#34;_blank&#34;&gt;Docker&lt;/a&gt; 和 &lt;a href=&#34;http://kubernetes.io/&#34; target=&#34;_blank&#34;&gt;Kubernates&lt;/a&gt;最近可以说红的发紫，各种大部头和高深研究也层出不穷。&lt;/p&gt;

&lt;p&gt;学习过程中看了官网和社区提供的不少起步文档，但是手工半手工这一块，总感觉语焉不详，操作性不很好，因此就边学边记，整理成这么一篇东西，本文只涉及操作，理论、架构、前景等内容，还请读者自行翻阅相关材料。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意这里按照我的个人习惯，会把所有可执行文件复制到 &lt;code&gt;/usr/local/share&lt;/code&gt;，并链接到 &lt;code&gt;/usr/local/bin&lt;/code&gt;；环境配置文件统一放置到 &lt;code&gt;/etc/sysconfig/kubernetes&lt;/code&gt; 中，这个做法跟手工安装一样，纯属个人恶趣味，没什么具体理由。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;

&lt;p&gt;这里我们使用的是 CentOS 7 为例子。&lt;/p&gt;

&lt;p&gt;接下来内容里面会使用 A, B 两台服务器：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;服务器 A

&lt;ul&gt;
&lt;li&gt;IP: 10.211.55.12&lt;/li&gt;
&lt;li&gt;职责&lt;/li&gt;
&lt;li&gt;Kubernate Master&lt;/li&gt;
&lt;li&gt;Docker Private Registry&lt;/li&gt;
&lt;li&gt;Kubernate Master UI&lt;/li&gt;
&lt;li&gt;Kubernate Node&lt;/li&gt;
&lt;li&gt;ETCD&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;服务器 B

&lt;ul&gt;
&lt;li&gt;IP: 10.211.55.13&lt;/li&gt;
&lt;li&gt;职责&lt;/li&gt;
&lt;li&gt;Kubernate Node&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;etcd-仅-master-需要&#34;&gt;ETCD （仅 Master 需要）&lt;/h3&gt;

&lt;p&gt;可以简单理解为，用来管理容器 IP 的数据库。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/coreos/etcd&#34; target=&#34;_blank&#34;&gt;Github 地址&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;使用 Yum 直接安装：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yum install -y etcd;&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：安装后需要修改 &lt;code&gt;/etc/etcd/etcd.conf&lt;/code&gt;，将其中的监听地址由 &lt;code&gt;127.0.0.1&lt;/code&gt; 改为 &lt;code&gt;0.0.0.0&lt;/code&gt; 或者其他 Node 可以访问的地址。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 启动服务
systemctl daemon-reload
systemctl enable etcd
systemctl start etcd

# 为后续服务提供初始值
etcdctl mk /docker.intranet/network/config &#39;{&amp;quot;Network&amp;quot;:&amp;quot;192.168.0.0/16&amp;quot;}&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;flannel-所有服务器都需要&#34;&gt;Flannel (所有服务器都需要)&lt;/h3&gt;

&lt;p&gt;对前面的 ETCD 有依赖，这里利用这一服务来为 Docker 提供网络分配和部分网络参数生成的任务。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yum install -y flannel&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;接下来编辑配置文件 &lt;code&gt;/etc/sysconfig/flanneld&lt;/code&gt;，将其中的 &lt;code&gt;FLANNEL_ETCD&lt;/code&gt; 地址修改为之前我们配置的地址，并修改 &lt;code&gt;FLANNEL_ETCD_KEY&lt;/code&gt; 为我们使用 etcdctl 设置的值，上文中是 &lt;strong&gt;&amp;ldquo;docker.intranet&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# 启动服务
systemctl daemon-reload
systemctl enable flanneld
systemctl start flanneld
&lt;/code&gt;&lt;/pre&gt;

&lt;hr /&gt;

&lt;h2 id=&#34;kubernates-master&#34;&gt;Kubernates Master&lt;/h2&gt;

&lt;h3 id=&#34;下载和安装&#34;&gt;下载和安装&lt;/h3&gt;

&lt;p&gt;首先下载压缩包，并复制其中需要的文件，然后做符号链接到习惯位置。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;wget https://github.com/kubernetes/kubernetes/releases/download/v1.1.3/kubernetes.tar.gz
cd kubernetes/server
tar xf kubernetes-server-linux-amd64.tar.gz
cp -Rf kubernetes/ /usr/local/share
cp kubernetes/cluster/centos/node/scripts/remove-docker0.sh /usr/local/share/kubernetes/server/bin
cd /usr/local/bin
ln -s /usr/local/share/kubernetes/server/bin/hyperkube
ln -s /usr/local/share/kubernetes/server/bin/kube-apiserver
ln -s /usr/local/share/kubernetes/server/bin/kube-controller-manager
ln -s /usr/local/share/kubernetes/server/bin/kubectl
ln -s /usr/local/share/kubernetes/server/bin/kubelet
ln -s /usr/local/share/kubernetes/server/bin/kube-proxy
ln -s /usr/local/share/kubernetes/server/bin/kube-scheduler
ln -s /usr/local/share/kubernetes/server/bin/linkcheck
ln -s /usr/local/share/kubernetes/server/bin/remove-docker0.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;主控服务器 A 除去前面提到的 ETCD 和 FlannelD 两个服务之外，需要三个服务&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Server&lt;/li&gt;
&lt;li&gt;Controller Manager&lt;/li&gt;
&lt;li&gt;Scheduler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;前面下载的包解压之后，&lt;code&gt;kubernetes/cluster/centos/master/scripts&lt;/code&gt; 中有以下脚本分别对应上面三个必要的服务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apiserver.sh&lt;/li&gt;
&lt;li&gt;controller-manager.sh&lt;/li&gt;
&lt;li&gt;scheduler.sh&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;这几个脚本大概看了一下，是自动安装过程的一部分，这里拿来进行修改，以适应我们自己的环境，方便应用，也有利于学习，并最终生成一个符合自己洁癖的环境:D&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;apiserver-sh&#34;&gt;apiserver.sh&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MASTER_ADDRESS&lt;/code&gt; 取值为服务器 A 的地址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ETCD_SERVERS&lt;/code&gt; 取值为ETCD的完整网址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SERVICE_CLUSTER_IP_RANGE&lt;/code&gt; 中的内容，按照之前我们给 ETCD 初始化的 IP 范围来设置。&lt;/li&gt;
&lt;li&gt;修改过时用法：&lt;code&gt;KUBE_API_ADDRESS=&amp;quot;--insecure-bind-address=${MASTER_ADDRESS}&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;修改过时用法：&lt;code&gt;KUBE_API_PORT=&amp;quot;--insecure-port=8080&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF&lt;/code&gt; 所在行涉及的文件名，按照个人习惯更改&lt;/li&gt;
&lt;li&gt;用于服务定义的 service 文件，其中的 &lt;code&gt;ExecStart&lt;/code&gt; 要注意修改为我们之前生成连接的位置。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后把涉及证书的一块删掉，这部分内容比较繁杂，先砍掉为好&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;KUBE_APISERVER_OPTS=&amp;quot;   \${KUBE_LOGTOSTDERR}         \\
                        \${KUBE_LOG_LEVEL}           \\
                        \${KUBE_ETCD_SERVERS}        \\
                        \${KUBE_API_ADDRESS}         \\
                        \${KUBE_API_PORT}            \\
                        \${MINION_PORT}              \\
                        \${KUBE_ALLOW_PRIV}          \\
                        \${KUBE_SERVICE_ADDRESSES}   \\&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;编辑结束之后，运行该脚本，也就完成了 API SERVER 的配置。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;语法错误等情况是在所难免的，可以使用 &lt;code&gt;systemctl status -l [服务名称]&lt;/code&gt; 来检查出错信息，如果信息不够详尽，可以把所生成的环境文件中的 &lt;code&gt;loglevel&lt;/code&gt; 设置为 0。&lt;/p&gt;

&lt;p&gt;另外还可以直接把 ExecStart 中的最终内容直接在命令行执行进行除错。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;controller-manager-sh-和-scheduler-sh&#34;&gt;controller-manager.sh 和 scheduler.sh&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;修改 &lt;code&gt;MASTER_ADDRESS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;修改文件位置&lt;/li&gt;
&lt;li&gt;执行&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;走到这里，就可以使用 &lt;code&gt;kubectl get nodes&lt;/code&gt; 之类的命令来跟 Master 互动了，浏览器也可以在服务器的 8080 端口获得一点没用的基本信息了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;docker&#34;&gt;Docker&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;本不想在 Master 机安装 Docker，但是后面的 Kubernates UI，如果不涉及网络操作的话，似乎必须在 Master 上运行，所以只能如此处理了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Docker 这里我们使用 Docker.io 提供的官方源进行安装：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# !bin/sh
tee /etc/yum.repos.d/docker.repo &amp;lt;&amp;lt;-&#39;EOF&#39;
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF

yum update -y
yum install -y docker-engine
systemctl daemon-reload
systemctl enable docker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个配置就不准备使用脚本生成了，会稍微麻烦一些，简单粗暴上代码：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;首先创建文件 &lt;code&gt;/etc/systemd/system/docker.service.d/custom.conf&lt;/code&gt; 这个文件会覆盖 docker.service 中的部分内容&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-conf&#34;&gt;[Service]
Environment=&amp;quot;HTTP_PROXY=10.211.55.2:8016&amp;quot; &amp;quot;NO_PROXY=localhost,center.docker.local,16.158.51.247&amp;quot;
EnvironmentFile=-/run/flannel/docker
EnvironmentFile=-/etc/sysconfig/kubernetes/docker
ExecStartPre=/usr/local/bin/remove-docker0.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上面文件中包含了代理的设置，用于在公司内网环境下，使用代理服务器获取外部仓库的内容。
两行&lt;code&gt;EnvironmentFile&lt;/code&gt;，其中一个是我们自行编写的 Docker 环境文件，另一个则是 flannel 运行生成的配置文件，而 &lt;code&gt;ExecStartPre&lt;/code&gt; 则是从 Kubernetes 压缩包中的 Docker 配置内容中抄来的，修改了文件位置而已。&lt;/p&gt;

&lt;p&gt;上面看出，Docker 服务对 Flanneld 服务是有依赖的，经过对 &lt;code&gt;systemctl status docker&lt;/code&gt; 的观察，可以看到这一服务在 &lt;code&gt;/usr/lib/systemd/system/docker.service.d&lt;/code&gt; 生成了一份文件，用于声明这一依赖关系。&lt;/p&gt;

&lt;p&gt;编辑这个文件：&lt;code&gt;/etc/systemd/system/multi-user.target.wants/docker.service&lt;/code&gt;
如果位置不同，可以使用 &lt;code&gt;find /etc -name &#39;docker.service&#39;&lt;/code&gt;，ExecStart 一行改为 &lt;code&gt;ExecStart=/usr/bin/docker daemon $DOCKER_OPT_BIP $DOCKER_OPT_MTU $DOCKER_OPTS&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;systemctl daemon-reload
systemctl enable docker
systemctl start docker
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;node&#34;&gt;Node&lt;/h2&gt;

&lt;p&gt;同样的，可以在 &lt;code&gt;kubernetes/cluster/centos/node/scripts&lt;/code&gt; 找到两个 Node 服务的配置脚本，这里先后修改运行 &lt;code&gt;kubelet.sh&lt;/code&gt; 以及 &lt;code&gt;proxy.sh&lt;/code&gt; 即可。&lt;/p&gt;

&lt;h2 id=&#34;kubeletes-ui&#34;&gt;Kubeletes UI&lt;/h2&gt;

&lt;p&gt;Kubeletes UI 的安装很简单：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl create -f cluster/addons/kube-ui/kube-ui-rc.yaml --namespace=kube-system
kubectl create -f cluster/addons/kube-ui/kube-ui-svc.yaml --namespace=kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;运行之后，可以使用 &lt;code&gt;kubectl get pods --all-namespace&lt;/code&gt; 查看进展状况，第一次创建，会到 Google 仓库下载基础镜像，前面介绍的代理使用技巧可能就派上用场了。等到状态从 Pending 变为 Running 之后，就可以用浏览器访问 &lt;code&gt;http://master:8080/ui&lt;/code&gt; 来查看控制台了。&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
