<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>deployment | 伪架构师</title>
    <link>/tags/deployment/</link>
      <atom:link href="/tags/deployment/index.xml" rel="self" type="application/rss+xml" />
    <description>deployment</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Sun, 28 Jul 2019 23:27:01 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>deployment</title>
      <link>/tags/deployment/</link>
    </image>
    
    <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>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>在 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>
    
  </channel>
</rss>
