# Deployment

Deployment资源是一种更为高阶的资源，它通过创建**ReplicaSet**的资源的方式，接管匹配标签的Pods。

通常我们使用Deployment来部署应用程序，从而方便**通过声明的方式升级应用**，即通过Deployment可以更容易地更新应用程序，通过声明Deployment资源需要到达的状态，Kubernetes会帮我们达到最终需要的状态。

## 创建Deployment

如其他资源一样，可以通过编写YAML配置文件的格式创建Deployment资源。

下面介绍一个具体的Deployment的YAML配置文件：

```yaml
apiVersion: apps/v1    # 注意版本信息
kind: Deployment    # 资源类型为Deployment
metadata:
  name: httpd
  namespace: default    # Deployment所在命名空间，默认为default
  labels:    # Deployment的标签
    run: httpd
spec:
  progressDeadlineSeconds: 600 # 滚动升级超时时间，默认为600s，超时后，describe结果中的Conditions会显示ProgressDeadlineExceeded。当超过这个时间后，需要使用kubectl rollout undo来回滚升级
  revisionHistoryLimit: 10    # 增加回滚记录中可以保存的版本数（因为Deployment更新后，不会删除旧的RS，所以这里其实就是保存的最大RS数）
  replicas: 3    # 产生的副本个数
  minReadySeconds: 10    # 用来减慢滚动更新的速度（容器需要至少Ready10s后才能认定其为可用，在Pod可用前，滚动升级过程不会继续），配合Readiness探针，可以实现当滚动升级过程中发现Pod出错，可以立即停止滚动更新
  strategy:
    type: RollingUpdate    # 更新策略（这种是删一个旧的，加一个新的，还有一种是Recreate，即全部删除旧的后，再创建新的）
    rollingUpdate:    # 定制滚动更新中的策略（适用于RollingUpdate，type为Recreate，则该字段为空）
      maxSurge: 35%        # 滚动更新中副本总数可超过DESIRED的上限（向上取整）
      maxUnavailable: 35%    # 滚动更新中不可用的副本占DESIRED的最大比例（向下取整）
  selector:        # （必须）用于匹配Pod的labels
    matchLabels:
      run: httpd
  template:    # （必须）Pod的模板
    metadata:
      labels:
        run: httpd
    spec:    # Pod的规格
      containers:
      - name: httpd
        image: httpd:2.4.17
        ports:
        - containerPort: 80    # 容器暴露的Port，其实这里只是一个类似于声明的，这里即使不写，容器的应用可能也暴露该端口
        env:    # docker的ENV
        - name: MYNAME
          value: http
      nodeSelector:    # 根据Nodes的label选择合适的Node部署Pods
        disktype: ssd
```

然后可以使用`kubectl apply -f`生成该资源。

通过`kubectl get deployments`可以看到创建出来的Deployment资源的名字为`httpd`，再通过`kubectl get rs`可以看到创建出来的RS的名字为`httpd-xxxxxx`，再通过`kubectl get pods`可以看到创建的Pods的名字为`httpd-xxxxxx-yyyy`（xxxxxxx和yyyy为自动生成的值）。

## 通过Deployment更新

通过Deployment进行滚动升级是声明式的，即只需要通过修改Deployment中的Pod模板，它会自动将集群中的状态收敛至该目标状态。

Deployment有两种更新策略，具体配置可以看上面的Deployment的YAML配置文件详情：

* `RollingUpdate`：默认策略，滚动升级，逐个删除旧Pod的同时创建新Pod；
* `Recreate`：一次性删除所有旧Pod，然后创建新Pod；

### 更新过程

之前提到，Deployment首先创建ReplicaSet，再由ReplicaSet负责创建并接管Pod。

升级Deployment非常容易，只需要修改资源的配置文件即可，修改方式见[**修改资源**](https://yangsijie151104.gitbook.io/k8s-note/ming-ling-hui-zong/chang-yong-ming-ling-hui-zong)。

![Deployment滚动升级流程](https://2906552408-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M6Ub8CloS5kJszh6xSR%2Fsync%2Fb040d2934f3ee02e308b64478e2b1f5b17d8d03e.png?generation=1588594619391768\&alt=media)

{% hint style="info" %}
**Tips:** `pod-template-hash`是Deployment创建RS时自动生成的，用来区分新旧Pod，这里与ReplicationController配合`rolling-update`进行滚动更新的方式有点像。但是这个标签对于Deployment和用户是无感的，因为用户直接对接的是Deployment，这也是为什么用Deployment更新应用的优势之一。
{% endhint %}

从上图中，可以将升级流程归纳为以下几步：

1. Deployment创建一个新的RS，负责创建并接管新的Pod，其与旧RS**具有不同值的pod-template-hash标签**；
2. 逐步对旧RS缩容的同时，对新RS扩容，直至更新完毕。
3. 在更新完毕后，会保存旧的RS，当使用Deployment进行回滚时，可以使用保存的旧的RS扩容旧的Pod的同时，使用新的RS缩容新的Pod。若Deployment接管的RS总数超过了`spec.revisionHistoryLimit`（默认为10），则删除保存时间最久的RS；

与[**ReplicationController滚动更新**](https://yangsijie151104.gitbook.io/k8s-note/kong-zhi-qi-controllers/controllers/replicationcontroller)对比，可以总结出以下优势：

* 更新流程均由Deployment这个控制器完成，与kubectl客户端解耦；
* 用户不用关心创建或删除RS，对用户无感，Deployment的标签无需修改；
* 由于Deployment可以保存历史RS，所以可以方便应用回滚；

#### maxSurge和maxUnavailable

在`RollingUpdate`这种滚动更新策略中，可以指定**maxSurge**和**maxUnavailable**两个属性。

* **maxSurge**：定义除Deployment的`spec.replicas`个Pod外，最多能超过的Pod数量，默认值为25%，向上取整。例如，`spec.replicas=4`，则`maxSurge=4*25%=1`，也就是说当前的最多的Pod数不超过4+1=5个；
* **maxUnavailable**：定义了**相对于Deployment的`spec.replicas`**&#x5141;许有多少Pod实例处于不可用状态，默认值为25%，向下取整。例如，`spec.replicas=4`，则`maxUnavailable=4*25%=1`，也就是说在滚动更新过程中，原本4个可用的Pod中，可以有一个被替换（若maxSurge计算得1，也就是Pod数不超过5，那么还可以再部署一个新的Pod，也就是同一时间可能有2个Pod不可用，3个Pod可用）。

下面举一个例子，更直观的观察滚动更新的流程，Deployment的`spec.replicas=4`，`maxSurge=25%`，`maxUnavailable=25%`：

![滚动更新中新旧Pod变化流程](https://2906552408-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M6Ub8CloS5kJszh6xSR%2Fsync%2F56c61b86efd2fa43ec70facb094abf758d7836de.png?generation=1588594618960307\&alt=media)

假设**A=maxSurge**，**B=maxUnavailable**，**X=spec.replicas**，那么可以得到下面的公式：

1. 更新过程中，最少可用Pod数 = X - B；
2. 每轮更新过程中，新RS可创建最大Pod数 = 旧RS可删除最大Pod数 = A + B；

{% hint style="info" %}
**Tips:** 说最大Pod数的原因是，需要考虑当前可用Pod数，如上图第一列，因为需要保证有3个可用Pod，所以旧RS在这里只能删除1个v1 Pod；倒数第二列，也是如此。
{% endhint %}

### 使用rollout控制更新流程

#### 观察升级过程

可以通过`rollout status`观察Deployment资源对象的升级过程。例如，观察Deployment对象kubia的升级过程：

```bash
kubectl rollout status deployment kubia
```

#### 查看滚动升级历史

可以通过`rollout history`查看Deployment资源对象的滚动升级历史。例如，观察Deployment对象kubia的滚动升级历史：

```bash
kubectl rollout history deployment kubia
```

{% hint style="info" %}
**Tips:** 默认滚动升级历史为空，需要在使用`kubectl create -f`或`kubectl apply -f`创建Deployment资源的同时加上`--record`标志，才会将该操作加入滚动历史中。
{% endhint %}

#### 回滚升级

可以通过`rollout undo`进行回滚，可以加上`--to-revision`标志，指定回滚的确切版本。例如，针对Deployment对象进行回滚：

```bash
kubectl rollout undo deployment kubia
kubectl rollout undo deployment kubia --to-revision=1
```

{% hint style="info" %}
**Tips:** `--to-revision`后面的版本号是滚动升级历史中的版本号。
{% endhint %}

#### 暂停和恢复升级流程

可以通过`rollout pause`暂停Deployment资源对象的滚动升级流程，从而方便用户验证新的版本行为是否符合预期，即**金丝雀版本（灰度）**，一部分为新版本，一部分为旧版本。例如，将Deployment对象kubia的滚动更新流程暂停：

```bash
kubectl rollout pause deployment kubia
```

恢复流程可以通过`rollout resume`来实现。例如：

```bash
kubectl rollout resume deployment kubia
```
