# 猫咪文档
> 探索技术文档和教程
================================
# 模块:Istio
Istio 是一个开源的服务网格平台,提供了流量管理、安全、可观察性等功能,帮助开发者更好地管理微服务架构。
--------------------------------
标题:文档说明
来源:/zh/istio/README
# 文档说明
作者:痴者工良
作者博客地址:
[https://www.whuanle.cn](https://www.whuanle.cn)
[https://www.cnblogs.com/whuanle](https://www.cnblogs.com/whuanle)
教程地址:[https://docs.whuanle.cn/zh/istio](https://docs.whuanle.cn/zh/istio)
## 导读
Istio 是构建在 Kubernetes 之上的服务网格(Service Mesh)基础设施,主要用于解决微服务系统中的**流量治理**、**可观测性**、**安全通信**等问题。
在很多团队里,应用虽然已经运行在 Kubernetes 上,但服务之间的调用治理、链路追踪、灰度发布、访问控制、熔断限流、入口出口流量管理等能力,往往还是散落在代码、SDK、网关配置或各种中间件中。这样做不仅增加了系统复杂度,也会让业务代码背上很多与业务无关的负担。
Istio 的价值就在于:**将一部分微服务治理能力下沉到基础设施层**。在尽量不改动业务代码的前提下,通过 Sidecar、网关和控制平面的配合,为服务提供统一的治理能力。
本教程围绕 Istio 的常见使用场景展开,尽量用循序渐进的方式带你理解:
- 为什么在微服务系统中需要 Istio;
- Istio 的核心组件和工作原理;
- 如何在 Kubernetes 中部署 Istio;
- 如何通过 Bookinfo 示例快速上手;
- 如何使用 VirtualService、DestinationRule、Gateway 等对象管理流量;
- 如何实现可观测性、金丝雀发布以及服务间认证鉴权。
本教程偏向**基础入门 + 实验实践**,适合把 Istio 当作服务治理学习入口的读者,也适合已经接触 Kubernetes、想进一步理解服务网格设计思想的读者。
## 本教程适合哪些读者
- 已经具备 Kubernetes 基础,想继续学习微服务治理;
- 想了解 Service Mesh 是什么、能解决什么问题;
- 希望掌握 Istio 常见资源对象及其使用方法;
- 想通过实验理解灰度发布、流量治理、认证授权等场景;
- 希望从架构角度理解“把治理能力下沉到基础设施层”这件事。
## 学习前建议
为了更顺畅地阅读本教程,建议你至少具备以下基础:
- 知道 Kubernetes 中 Pod、Deployment、Service、Namespace 的作用;
- 能够使用 `kubectl` 查看和部署资源;
- 对微服务、HTTP 调用、网关有基本概念;
- 最好已经有一个可用的 Kubernetes 练习环境。
如果你还没有接触过 Kubernetes,建议先补充基础知识后再学习本教程,这样会更容易理解 Istio 为什么这样设计。
## 你将学到什么
学习完本教程后,你可以对以下内容建立起整体认识:
- Istio 在微服务体系中的定位,以及它与 Kubernetes 的关系;
- Sidecar、Envoy、数据平面、控制平面的基本概念;
- 使用 Helm 安装和管理 Istio 组件;
- 给命名空间启用 Sidecar 自动注入;
- 使用 Gateway、VirtualService、DestinationRule 控制入口与服务路由;
- 使用 Kiali、Jaeger、Prometheus 等组件观察网格流量;
- 实现基于版本、Header、比例的流量分发;
- 实现超时、故障注入、熔断等流量治理能力;
- 实现 mTLS、请求认证、授权控制等安全能力。
## 学习路线建议
如果你是第一次接触 Istio,建议按照下面的顺序阅读:
1. 先阅读“为什么学习 Istio”,理解 Istio 出现的背景和解决的问题;
2. 再完成 Istio 的基础部署,先把环境搭起来;
3. 通过 Bookinfo 示例快速上手,建立对 Gateway、VirtualService、Sidecar 的直观认识;
4. 接着学习可观测性,观察服务调用关系和链路追踪数据;
5. 在此基础上深入学习流量管理、网关和金丝雀发布;
6. 最后学习认证与授权,补齐服务网格中的安全能力。
这样阅读,会更容易从“能跑起来”逐步走向“理解原理”和“掌握场景”。
## 阅读与实践建议
- 建议边看边做实验,不要只停留在概念层面;
- 学习流量治理时,尽量配合 Kiali 一起观察调用拓扑;
- 修改配置后,多使用 `kubectl get`、`kubectl describe`、`kubectl logs` 检查运行状态;
- 对比“未使用 Istio”和“使用 Istio”两种方式,更容易体会服务网格带来的变化;
- Istio 能力很多,但不必一开始追求全部掌握,先把最常用的入口、路由、观测、安全学扎实即可。
## 目录
* [1.为啥学习 Istio](1.start.md)
* 微服务治理背景、Istio 的价值、核心原理
* [2.部署 Istio](2.deploy.md)
* 使用 Helm 部署基础组件和入口网关
* [3.快速入门 Istio](3.try.md)
* 使用 Bookinfo 示例完成第一次实践
* [3.1.可观察性](3.1.tlm.md)
* 使用 Kiali、Jaeger、Prometheus、Grafana 观察网格状态
* [4.流量管理](4.traffic.md)
* 学习路由、故障注入、超时、熔断、流量比例分配
* [4.1.VirtualService 的定义](4.1.vs.md)
* 深入理解 VirtualService 和 DestinationRule
* [5.网关](5.gateway.md)
* 学习入口网关和出口网关的使用方式
* [6.金丝雀发布](6.jsq.md)
* 学习灰度发布和按条件分流
* [7.认证](7.safe.md)
* 学习认证、授权与服务间安全通信
希望本系列教程能够帮助你不仅学会 Istio 的基础使用方式,也能借此理解微服务治理与服务网格背后的设计思想。
--------------------------------
标题:Istio 概述
来源:/zh/istio/1.start
# 1,Istio 概述
### 🚩聊聊微服务设计
似乎用上 Kubernetes ,就是微服务系统了。
碰到很多人或公司盲目崇拜 Kubernetes ,一直喊着要上 Kubernetes,但是本身既没有技术储备,也没有规划方案。想着上了 Kubernetes 之后,就会变成分布式、高性能、高逼格的微服务系统。
从经验来看,很多公司用上 Kubernetes 之后,并不会显著改善旧系统的缺点,而由于项目中充斥着大量泥球般混乱的代码、随意使用数据库事务、几百行代码的函数、过度引用其它项目的接口等,无论在 Kubernetes 上放置多少个应用实例,速度总是提示不起来。
【图源互联网】
其实这种情况是挺常见的,例如你公司的项目,跟我公司的项目。
在笔者经历当中,碰到过很多中小公司开始从单体系统转型为使用 Kubernetes 设施的系统,然而大多数对 Kubernetes 的使用局限于表面,下面来聊聊几种常见的情况。
##### 🥇只使用 Kubernetes 部署容器,
只使用 Kubernetes 部署容器,其它方面几乎没有变化。
此类系统加入了 Jenkins 构建 CICD 容器,构建后部署到 Kubernetes 中,但是未考虑容错处理、内外部系统通讯、没有使用可观察性系统(监控、日志、链路追踪),也没有服务发现和负载均衡。整套系统仅仅是拆分成了多个服务部署,服务之间的通讯使用固定地址写死配置文件,或者应用修改配置很麻烦。
此类系统唯一亮点是使用了 Kubernetes,可能还用上了 Redis、Mongodb 之类的数据存储系统。
可是,由于缺少合理的架构设计,系统虽然拆分出来了,其带来的好处只不过是研发小组可以包干一个项目,方便管理研发工作。
为了应对子服务通讯需要,只能不断在用户中心或其它服务增加一大套 API,以便子服务能够获取需要的信息。可是其拆分后带来的弊端更多,服务拆分造成了数据隔离(一般是同一个数据库引擎,使用不同的数据库名称),每个服务都有自己的数据库,这个时候,他们还往往忽略了分布式下的可能会出现的数据一致性问题、分布式事务问题等。
由于其缺乏足够的基础设施支撑,当服务通讯出现错误时排查问题也会变得十分困难,该项目组可能需要跟不同的小组协调排查,有可能为了一个问题修改十多次代码,不断重新部署不断追加日志,以便查找问题修复 bug。
##### 🥈开始使用一些中间件,完善基础设施
为了解决微服务系统中的一些问题,研发团队引入了一些中间件。
**数据同步**:为了解决数据隔离问题,引入了 Canal 此类数据库同步工具,例如将用户中心的用户表同步到下游,子服务可以直接在自己的数据库 join 数据,聚合信息变得更加容易。为了更加容易查询聚合数据,还将 Mysql 数据消费后同步到 ES、Kafka 等系统进行二次处理。
**自动化部署与持续集成**:微服务的部署实现自动化,以减少人工干预和错误。
**监控与日志**:集群中使用了分布式监控和日志记录,以便于跟踪和诊断问题。**【Istio 可以帮到您】**
**服务发现与负载均衡**:使用了 Consul 等服务注册和发现中间件,解决了服务通讯地址耦合配置的问题,无需人工维护服务地址列表,还带有健康检查、负载均衡等功能。**【Istio 可以帮到您】**
**配置管理**:使用 Nacos、 Apollo 等配置中心,能够动态变更服务的配置。
**服务划分不合理**:在微服务架构中,将系统拆分成多个独立的服务是至关重要的,然而服务划分不合理可能导致服务过度拆分或功能耦合。
**数据一致性**:由于微服务使用独立的数据存储,可能导致数据一致性问题。
**高度耦合**:服务间过度耦合可能导致修改一个服务会影响到其他服务,降低了系统的可维护性。
**难以诊断与监控**:由于微服务的分布式特性,系统出现问题时可能难以诊断和监控。**【Istio 可以帮到您】**
**安全性问题**:微服务间的通信可能导致安全性问题,大多数系统没有考虑到外部通讯鉴权、内部系统通讯鉴权的问题。**【Istio 可以帮到您】**
此类系统可以解决在服务通讯和服务故障中出现的一些问题,也提供减轻研发人员负担的中间件。
但是此类系统,可能还有很多问题。笔者下面说说所接触到的实际例子。
首先是代码耦合了太多组件。例如为了支持链路追踪、日志收集,代码中引用了很多相关的类库并且需要进行复杂的配置。
**其实这些组件是可以下沉到基础设施的,这也正是笔者编写 Istio 教程的原因。**
.NET 中的 ABP 框架也许正是被批评的对象之一,因为 ABP 真的太 “重” 了,想想 ABP 这么多的组件,每个模块都需要添加代码配置使用,项目光是配置这些模块、扩展服务就得写多少代码,学习成本得有多高。
想想,项目这么多模块和配置,能记得多少,每次新建一个项目都要从别的项目那里复制一堆配置和代码过去,累不。
此外,微服务之间通过网络通信,没有好的故障处理方案,也是一个常见的情况。微服务通讯可能会出现网络延迟和故障,需要采取设施使用超时、重试、熔断等容错策略来降低网络问题的影响。
实际情况下,大多数研发团队并不会处理这些问题,一个子服务直接发出 HTTP 请求,如果请求失败就直接抛出异常,或者使用的方法是在代码中加入一些组件,当请求失败时程序自动处理故障,如重试、熔断等。
> 例如 C# 可以使用 Polly 组件,配置对 HTTP 请求的超时、重试等策略。可是配置是写死在代码中的,如果需要变化就需要修改代码,而且研发人员不一定能够预先配置好足有优秀的参数。多加一个组件,程序的 “重量” 增加一分,维护难度也会加大。
所以说,要设计一套微服务系统并不容易。
### ❓我为什么要学 Istio
首先是,Istio 能够将很多配置下沉到基础设施层面,我们的项目代码可以减少很多组件、代码和配置。
熟话说得好,架构设计决定了系统的上限,而实现细节决定了系统的下限。
想想,如果你使用 ABP 来开发业务,当你打开项目代码时,里面的配置还记得清楚吗?每次新建项目,都要从旧项目抄一堆代码和配置,还需要在各种中间件中增加相当多的配置。笔者看过不少使用 ABP 编写的项目,里面的配置实在太多了。况且涉及的依赖包很多,不同的项目模块更新时也很容易碰到组件版本不一致导致的冲突。
前面说了,微服务通讯时,需要自动故障修复,能够自动重试、熔断,并且还需要支持服务的健康检查以及负载均衡。如果这些都在代码中配置,每个项目都需要重复劳动一次。
所以,我学习 Istio 主要原因有三点:
* 将代码中的东西丢到基础设施中去,减少代码量和复杂的配置。
* 可以学习到很多微服务设计思想。
* 更加合理地在 Kubernetes 上设计系统架构。
当然,按照公司的体量和业务支撑需求,并不一定用得上 Istio,而本身 Istio 也挺重的,学起来难度不比 Kubernetes 低。
在写本文之前,跟一个大佬聊天,他说大多数公司用不上 Istio,而用得上的公司会选择自研。其实用不用得上并不重要,重要的是你可以从学习 Isito 的时候,了解很多设计思想,了解 Istio 的这些组件解决了什么样的问题。之后,除了使用 Isito ,我们也可以使用其它方法来解决这些问题。
### 💡所以,Istio 是什么
Istio 是一个与 Kubernetes 紧密结合的服务网格(Service Mesh),用于**服务治理**。
注意,Istio 是用于服务治理的,主要有流量管理、服务间安全、可观测性这几种功能。在微服务系统中,会碰到很多棘手的问题,Istio 只能解决其中一小部分。
服务治理有三种方式,第一种是每个项目中包含单独的治理逻辑,这样比较简单;第二种是将逻辑封装到 SDK 中,每个项目引用 SDK 即可,不增加或只需要少量配置或代码即可;第三种是下沉到基础设施层。Istio 便是第三种方式。

Istio 对业务是非侵入式的,**完全不需要改动项目代码**。
#### Istio 三个主要功能
接下来介绍一下 Istio 的三个主要能力。
**⏩流量管理**
流量管理包括以下功能:
- 动态服务发现
- 负载均衡
- TLS 终端
- HTTP/2 与 gRPC 代理
- 熔断器
- 健康检查
- 基于百分比流量分割的分阶段发布
- 故障注入
- 丰富的指标
**⏩可观测性**
Istio 支持 Jaeger、Zipkin、Skywalking 等链路追踪中间件,支持 Prometheus 收集指标数据,然后日志功能没有上面亮点,只是记录了请求的 HTTP 地址之类。Istio 的可观测性帮助我们了解应用程序的性能和行为,使得故障检测、性能分析和容量规划变得更加简单。
**⏩安全性能**
主要特点是可以实现零信任网络中的服务之间通讯加密。Istio 通过自动为服务之间的通信提供双向 TLS 加密来增强安全性,同时 Istio 还提供了强大的身份验证、授权和审计功能。
#### Istio 原理
Istio 可以的作用原理是拦截 Kubernetes 部署 Pod 的事件,然后从 Pod 中注入一个名为 Envoy 的容器,**这个容器会拦截外部到业务应用的流量**。由于所有流量都被 Envoy “劫持” 了,所以 Istio 可以对流量进行分析例如收集请求信息,以及一系列的流量管理操作,也可以验证授权信息。当 Envoy 拦截流量并执行一系列操作之后,如果请求没问题,就会转发流量到业务应用的 Pod 中。

【左:普通 Pod;Istio;右:Istio 代理了出入口流量】
> 当然,由于 Envoy 需要拦截流量之后转发给业务应用,这样就多了一层转发,会导致系统响应速度会有所下降,**但是增加的响应时间几乎可以忽略不计**。
每个 Pod 都有一个 Envoy 负责拦截、处理和转发进出 Pod 的所有网络流量,这种方式被称为 Sidecar。
以下是 Istio Sidecar 的一些主要功能:
* 流量管理:Envoy 代理可以根据 Istio 配置的路由规则(如 VirtualService 和 DestinationRule)实现流量的转发、分割和镜像等功能。
* 安全通信:Envoy 代理负责在服务之间建立安全的双向 TLS 连接,确保服务间通信的安全性。
* 遥测数据收集:Envoy 代理可以收集关于网络流量的详细遥测数据(如延迟、成功率等),并将这些数据上报给 Istio 的遥测组件,以便进行监控和分析。
* 策略执行:Envoy 代理可以根据 Istio 配置的策略规则(如 RateLimit 和 AuthorizationPolicy)执行限流、访问控制等策略。
由于 Pod 是通过 Envoy 暴露端口的,所有进出口流量都需要经过 Envoy 的检查,所以很容易判断访问来源,**如果请求方不是在 Istio 中的服务,那么 Envoy 便会拒绝访问。**

在 Istio 中,Envoy 这一块称为数据平面,而负责管理集群的 istiod 组件称为控制平面。
> 注意,这里是 istiod ,是 Istio 负责管理集群的一种组件。
对 Istio 的介绍就到这里为止,在后面的章节中,我们会通过实际使用 Istio ,来进一步深入了解 Istio 的功能和原理。
--------------------------------
标题:部署 Istio
来源:/zh/istio/2.deploy
# 2,部署 Istio
在本章中,将会介绍如何在 Kubernetes 中使用 Helm 部署 Istio。
Istio 的安装方式主要有两类,第一类是基于 Kubernetes 原生集群或虚拟机的安装。另一种是基于 Azure、KubeSphere 等公私有云或 Kubernetes 管理平台上的安装。而在本章中介绍的是基于 Kubernetes 的 Helm 安装。
Istio 官网关于这两类部署方式还有很多小细节,读者可根据实际需要从官方中获取部署资料。
> https://istio.io/latest/zh/docs/setup/platform-setup/
>
> https://istio.io/latest/zh/docs/setup/install/
### 安装 Helm
首先添加 Helm 官方仓库地址到 apt 源中。
```bash
curl https://baltocdn.com/helm/signing.asc | sudo apt-key add -
echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
```
然后更新包索引。
```bash
apt-get update
```
通过 apt 命令安装 Helm。
```bash
apt-get install helm
```
验证是否安装完成。
```bash
helm version
```

### 部署 istio-base
Google 对 istio-base 的描述信息太少了,只得请教一下 ChatGPT 哥。

也就是说,只有先安装 istio-base,才能接着安装其它 Istio 组件。
> 在本文教程中,安装的 Istio 与官方使用 istiocli 部署的方式不同,本教程中是逐渐安装需要的组件,不会一次性安装完成所有组件。这样便于读者逐步了解不同的 Istio 组件的作用,了解其安装方式。
在 Helm 中 添加 Istio 的仓库。
```bash
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update
```
接着提前为 Istio 组件创建命名空间 `istio-system`。
```bash
kubectl create namespace istio-system
```
接下来将使用 Helm 将 Istio 组件安装到 istio-system 命名空间中。
首先安装 Istio CRD:
```bash
helm install istio-base istio/base -n istio-system
```
```bash
root@k8smain:~# helm install istio-base istio/base -n istio-system
NAME: istio-base
LAST DEPLOYED: Tue May 2 07:19:15 2023
NAMESPACE: istio-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Istio base successfully installed!
To learn more about the release, try:
$ helm status istio-base
$ helm get all istio-base
```
使用 `helm ls` 命令验证 Istio CRD 的安装情况:
```bash
root@k8smain:~# helm ls -n istio-system
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
istio-base istio-system 1 2023-05-02 07:19:15.792125237 +0000 UTC deployed base-1.17.2 1.17.2
```
如果 `STATUS` 的内容是 `deployed` ,说明已经正常,接下来我们进行下一步操作。
### 部署 istiod
Istiod( Istio Discovery) 是 Istio 服务网格的核心组件,负责**控制平面功能**。
istiod 具备了五大功能:
* 配置管理:负责分发和同步 Istio 配置到数据平面(Envoy 代理)。
* 服务发现:基于 Kubernetes 的 Service 和 Endpoint 信息生成服务发现数据,这些数据用于 Envoy Proxy 的负载均衡。
* 证书管理:为 Envoy Proxy 提供证书签发,以支持双向 TLS 身份验证。
* 验证和转换:验证 Istio 配置资源的正确性,并将它们转换为 Envoy Proxy 可以理解的格式。
* Envoy 代理注入:负责将 Envoy Proxy 注入到服务 Pod 中,以便进行流量拦截和路由。
> 简单看一下就好了,不用记。
**新版本的 Istiod 将旧版本中零散的组件如 Mixer、Pilot、Citadel、Galley 等合并起来了**,所以在网上看书查找资料的时候,要注意规避过旧的内容。
在 Helm 中添加 Istiod 仓库。
```bash
helm install istiod istio/istiod -n istio-system --wait
```

验证 Istiod 的安装情况:
```bash
root@k8smain:~# helm ls -n istio-system
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
istio-base istio-system 1 2023-05-02 07:19:15.792125237 +0000 UTC deployed base-1.17.2 1.17.2
istiod istio-system 1 2023-05-02 07:21:07.791242626 +0000 UTC failed istiod-1.17.2 1.17.2
```
检查 `istiod` 服务是否安装成功,其 Pod 是否正在运行:
```bash
root@k8smain:~# kubectl get deployments -n istio-system -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
istiod 1/1 1 1 10m discovery docker.io/istio/pilot:1.16.1 istio=pilot
```
### 部署 istio-ingressgateway
istio-ingressgateway (Istio Ingress Gateway )类似 Kubernetes 的 Ingress ,是 Istio 控制外部流量进入 Kubernetes 的入口组件,istio-ingressgateway 作为一个入口点,允许从服务网格外部访问服务网格内部的服务,起到了类似 nginx、apisix 等入口网关的作用。
Istio Ingress Gateway 的主要包括以下作用:
* 接收集群外部的流量,并根据 Istio 的配置将请求路由到适当的内部服务(起到网关的作用)。
* 提供负载均衡和流量控制功能,包括请求路由、重试、超时、熔断等(流量治理)。
* 支持 TLS 配置,以便在流量进入服务网格之前进行加密(给域名配置证书)。
* 支持双向 TLS 身份验证,以提高服务网格的安全性(服务间通讯)。
* 提供 Metrics、Tracing 和 Logging 收集,以便更好地观察和监控流量(需要自己安装对应的组件)。
> 随便看看就好,不用记这些。
安装 istio-ingressgateway。
```bash
helm install istio-ingressgateway istio/gateway -n istio-system
```

实际上 istio-ingressgateway 是作为一个 Kubernetes Service 对外提供访问服务。

由于 Istio-ingressgateway 默认使用的是 LoadBalancer ,需要公有云平台支撑,不然会一直处于 ``,因此我们需要修改 Service ,将 istio-ingressway 的网络类型从 LoadBalancer 改成 NodePort,以便直接通过服务器的 IP 访问。
```bash
kubectl edit svc istio-ingressgateway -n istio-system
```
找到 `type: LoadBalancer` ,修改为 `type: NodePort`。

> 因为 `LoadBalancer` 包含了 `NodePort`,其实不修改也行。
istio-ingressgateway 本身包含 Kubernetes Service 、Pod,通过暴露节点端口,外部可以通过节点端口将流量打入 istio-ingressgateway 的 Pod。

流量经过 Istio 分析后,流量通过负载均衡转发到其中一个 Pod。

> 流量进入 Istio 之后,不需要将流量转发到 Service,但是依然需要依赖 Service。 Istio 会从 Service 中获取到所有的 Pod,然后 Istio 直接将流量转发到 Pod,实现熔断、故障处理等一系列任务。
经过以上步骤,我们已经安装和了解 istio-base、istiod、istio-ingressgateway 三个 Istio 基础组件,在后面的章节中,我们将开始真正实践使用 Istio ,去解决微服务中的一些问题。
### 清除
如果有一天不需要 Istio 了,你可以通过当前命令清空部署的 Istio 应用。
```bash
helm delete istio-ingressgateway -n istio-system
helm delete istiod -n istio-system
helm delete istio-base -n istio-system
kubectl delete namespace istio-system
```
--------------------------------
标题:快速入门
来源:/zh/istio/3.try
# 3,快速入门
在本章中,我们正式迈入学习 Istio 的第一步。因为 Istio 的知识体系是较为庞大的,因此我们可以先通过本章的入门教程快速了解如何使用 Istio 部署一套微服务,以及 Istio 核心功能的使用方法,了解 Istio 可以为微服务解决什么问题。
在本章中,我们将会学习到如何部署一套微服务、如何使用 Istio 暴露服务到集群外,并且如何使用可观测性组件监测流量和系统指标。
在后面的章节中,笔者会针对每个 Istio 组件做单独讲解,而在本章中,我们只需要大概了解使用方法即可。
### 书店微服务
本章教程示例使用的是 Istio 官方的一套微服务,这套微服务是一个在线书店,打开页面之后会看到一个分类、书的信息以及书的评论,页面的内容由不同的子服务提供。

书店微服务分为四个单独的微服务,在上图中已经使用红色方框画出来了。这四个微服务分别是:
- `productpage`: 汇集所有服务的数据,生成浏览页面。
- `details`:存储了书籍的信息,如描述、作者、出版社等。
- `reviews`:存储了书籍相关的评论,但是不包含评分打星。
- `ratings`:存储评论中的评分打星。
在这个微服务中,Productpage 服务对外提供 Web 访问页面,**而且其它的三个服务只能在集群内部访问**。四个服务分别采用了不同的语言开发,Productpage 聚合其它三个服务的信息生成一个页面。
> 在微服务设计中,我们不要每个子服务都暴露端口到集群外部,应该通过一些应用集中数据后给外部显示。我们可以使用 API 网关,代理子服务一部分接口,然后在 API 网关中实现基于客户端或第三方调用的身份验证。
productpage、details、ratings 都只有一个 v1 版本,而 reviews 有三个版本。
ratings 负责给出用户打分的数据,例如一星、两星。而 reviews 三个版本分别对 ratings 的数据做以下处理:
* reviews v1:屏蔽星级,不显示打分;
* reviews v2:显示星级,使用灰色星星表示,★★★★☆;
* reviews v3:显示星级,使用红色星星表示,★★★★☆;

【图源 istio 官网 】
服务依赖图如下所示:

接下来我们将会使用 Kuubernetes Deployment 部署这些服务,这跟常规的 Kubernetes 部署并无差别。
#### 预先准备
给这些示例服务创建一个命名空间。
```bash
kubectl create namespace bookinfo
```
给命名空间添加 Istio 的标签,指示 Istio 在部署应用(只对 Pod 起效)的时候,自动注入 Envoy Sidecar Proxy 容器:
```bash
kubectl label namespace bookinfo istio-injection=enabled
```
> 开启让 Istio 注入 Sidecar 有很多种方式,其中一种是给命名空间设置下标签,在此命名空间下部署的 Pod,会被自动注入 Sidecar 。
你可以从本系列教程的 git 仓库中找到这些示例,文件位置:[https://github.com/whuanle/istio_book/tree/main/3](https://github.com/whuanle/istio_book/tree/main/3)。
仓库拉取后打开 `3` 目录,执行命令进行部署:
```bash
kubectl -n bookinfo apply -f details_deploy.yaml
kubectl -n bookinfo apply -f details_svc.yaml
kubectl -n bookinfo apply -f details_sa.yaml
kubectl -n bookinfo apply -f ratings_deploy.yaml
kubectl -n bookinfo apply -f ratings_svc.yaml
kubectl -n bookinfo apply -f ratings_sa.yaml
kubectl -n bookinfo apply -f reviews_v1_deploy.yaml
kubectl -n bookinfo apply -f reviews_v2_deploy.yaml
kubectl -n bookinfo apply -f reviews_v3_deploy.yaml
kubectl -n bookinfo apply -f reviews_svc.yaml
kubectl -n bookinfo apply -f reviews_sa.yaml
kubectl -n bookinfo apply -f productpage_deploy.yaml
kubectl -n bookinfo apply -f productpage_svc.yaml
kubectl -n bookinfo apply -f productpage_sa.yaml
```
或者你可以参考下面的四个小节,通过手动的方式部署应用,了解每一个应用是如何定义的。
#### details 应用
存储了书籍信息的应用。
部署命令:
```bash
kubectl -n bookinfo apply -f details_deploy.yaml
kubectl -n bookinfo apply -f details_svc.yaml
kubectl -n bookinfo apply -f details_sa.yaml
```
使用 Deployment 部署 details 应用。
`details_deploy.yaml`
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: details-v1
labels:
app: details
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: details
version: v1
template:
metadata:
labels:
app: details
version: v1
spec:
serviceAccountName: bookinfo-details
containers:
- name: details
image: docker.io/istio/examples-bookinfo-details-v1:1.17.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
securityContext:
runAsUser: 1000
```
部署 details。
```bash
kubectl -n bookinfo apply -f details_deploy.yaml
```
为 details 服务配置 Kubernetes Service 。
`details_svc.yaml`
```yaml
apiVersion: v1
kind: Service
metadata:
name: details
labels:
app: details
service: details
spec:
ports:
- port: 9080
name: http
selector:
app: details
```
```bash
kubectl -n bookinfo apply -f details_svc.yaml
```
接下来为 details 服务创建一个 ServiceAccount。
> Istio 为服务之间的通信提供基于双向 TLS 的认证,这是是通过给每个 ServiceAccount 创建一个证书实现的,可以使用 ServiceAccount 验证对方的身份,不同的应用可以共享同一个 ServiceAccount,但是为每个 Deployment 使用单独的 ServiceAccount 可以更好地组织和管理安全配置。
`details_sa.yaml `
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: bookinfo-details
labels:
account: details
```
```bash
kubectl -n bookinfo apply -f details_sa.yaml
```
#### ratings 应用
提供每条评论的打星数据。
部署命令:
```bash
kubectl -n bookinfo apply -f ratings_deploy.yaml
kubectl -n bookinfo apply -f ratings_svc.yaml
kubectl -n bookinfo apply -f ratings_sa.yaml
```
`ratings_deploy.yaml`
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ratings-v1
labels:
app: ratings
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: ratings
version: v1
template:
metadata:
labels:
app: ratings
version: v1
spec:
serviceAccountName: bookinfo-ratings
containers:
- name: ratings
image: docker.io/istio/examples-bookinfo-ratings-v1:1.17.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
securityContext:
runAsUser: 1000
```
`ratings_svc.yaml`
```yaml
apiVersion: v1
kind: Service
metadata:
name: ratings
labels:
app: ratings
service: ratings
spec:
ports:
- port: 9080
name: http
selector:
app: ratings
```
`ratings_sa.yaml`
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: bookinfo-ratings
labels:
account: ratings
```
#### reviews v1/v2/v3 应用
提供书籍的评论信息。
部署命令:
```bash
kubectl -n bookinfo apply -f reviews_v1_deploy.yaml
kubectl -n bookinfo apply -f reviews_v2_deploy.yaml
kubectl -n bookinfo apply -f reviews_v3_deploy.yaml
kubectl -n bookinfo apply -f reviews_svc.yaml
kubectl -n bookinfo apply -f reviews_sa.yaml
```
为三个版本的 reviews 创建三个 Deployment。
`reviews_v1_deploy.yaml`
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: reviews-v1
labels:
app: reviews
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v1
template:
metadata:
labels:
app: reviews
version: v1
spec:
serviceAccountName: bookinfo-reviews
containers:
- name: reviews
image: docker.io/istio/examples-bookinfo-reviews-v1:1.17.0
imagePullPolicy: IfNotPresent
env:
- name: LOG_DIR
value: "/tmp/logs"
ports:
- containerPort: 9080
volumeMounts:
- name: tmp
mountPath: /tmp
- name: wlp-output
mountPath: /opt/ibm/wlp/output
securityContext:
runAsUser: 1000
volumes:
- name: wlp-output
emptyDir: {}
- name: tmp
emptyDir: {}
```
`reviews_v2_deploy.yaml`
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: reviews-v2
labels:
app: reviews
version: v2
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v2
template:
metadata:
labels:
app: reviews
version: v2
spec:
serviceAccountName: bookinfo-reviews
containers:
- name: reviews
image: docker.io/istio/examples-bookinfo-reviews-v2:1.17.0
imagePullPolicy: IfNotPresent
env:
- name: LOG_DIR
value: "/tmp/logs"
ports:
- containerPort: 9080
volumeMounts:
- name: tmp
mountPath: /tmp
- name: wlp-output
mountPath: /opt/ibm/wlp/output
securityContext:
runAsUser: 1000
volumes:
- name: wlp-output
emptyDir: {}
- name: tmp
emptyDir: {}
```
`reviews_v3_deploy.yaml`
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: reviews-v3
labels:
app: reviews
version: v3
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v3
template:
metadata:
labels:
app: reviews
version: v3
spec:
serviceAccountName: bookinfo-reviews
containers:
- name: reviews
image: docker.io/istio/examples-bookinfo-reviews-v3:1.17.0
imagePullPolicy: IfNotPresent
env:
- name: LOG_DIR
value: "/tmp/logs"
ports:
- containerPort: 9080
volumeMounts:
- name: tmp
mountPath: /tmp
- name: wlp-output
mountPath: /opt/ibm/wlp/output
securityContext:
runAsUser: 1000
volumes:
- name: wlp-output
emptyDir: {}
- name: tmp
emptyDir: {}
```
给三个 Deployment 创建一个 Service,三个相同应用的不同版本共有同一个 Service。
`reviews_svc.yaml`
```yaml
apiVersion: v1
kind: Service
metadata:
name: reviews
labels:
app: reviews
service: reviews
spec:
ports:
- port: 9080
name: http
selector:
app: reviews
```
`reviews_sa.yaml`
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: bookinfo-reviews
labels:
account: reviews
```
#### productpage 应用
页面聚合服务,供用户浏览书籍信息。
部署命令:
```bash
kubectl -n bookinfo apply -f productpage_deploy.yaml
kubectl -n bookinfo apply -f productpage_svc.yaml
kubectl -n bookinfo apply -f productpage_sa.yaml
```
`productpage_deploy.yaml`
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: productpage-v1
labels:
app: productpage
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: productpage
version: v1
template:
metadata:
labels:
app: productpage
version: v1
spec:
serviceAccountName: bookinfo-productpage
containers:
- name: productpage
image: docker.io/istio/examples-bookinfo-productpage-v1:1.17.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
volumeMounts:
- name: tmp
mountPath: /tmp
securityContext:
runAsUser: 1000
volumes:
- name: tmp
emptyDir: {}
```
`productpage_svc.yaml`
```yaml
apiVersion: v1
kind: Service
metadata:
name: productpage
labels:
app: productpage
service: productpage
spec:
ports:
- port: 9080
name: http
selector:
app: productpage
```
`productpage_sa.yaml`
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: bookinfo-productpage
labels:
account: productpage
```
### 检查
执行命令完成后,查看 bookinfo 命名空间下的 Pod。
```bash
kubectl get pods -n bookinfo
```

可以看到,每个 Pod 的 READY 属性都是 `2/2` ,这表示该 Pod 中有两个容器,并且当前有两个容器已经就绪。
如果我们查看其中一个 Pod 的组成结构,会发现有 Pod 被塞进了一个 istio-proxy 容器。

> 如果 Kubernetes 中没有安装 Dashbooard ,那么可以使用 `kubectl -n bookinfo describe pod {Pod ID}` 查看组成结构。
接着使用 `kubectl -n bookinfo get svc` 查看 Service,四个微服务都已经被注册了 Service。

然后我们访问 productpage 对应的 CLUSTER-IP:
```bash
curl 10.233.37.130:9080
```
> 默认 Istio 不会开启零信任双向认证模式,因此在集群内可以自己访问应用。如果开启了 mTLS 双向认证模式,则只能在 Pod 中访问应用。
可以看到返回了一堆 html,说明我们的部署是正常的。
#### 临时访问
接着为了查看页面效果,我们在暂未使用 Istio-ingressgateway 之前,临时创建一个 Service 暴露 productpage 的页面。
`productpage_tmpsvc.yaml`
```yaml
apiVersion: v1
kind: Service
metadata:
name: productpagetmp
labels:
app: productpage
service: productpage
spec:
ports:
- port: 9080
name: http
selector:
app: productpage
type: NodePort
```
```bash
kubectl -n bookinfo apply -f productpage_tmpsvc.yaml
```
查看所有 Service:
```bash
root@k8smain:/data/learn/book# kubectl -n bookinfo get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
details ClusterIP 10.233.63.247 9080/TCP 40m
productpage ClusterIP 10.233.37.130 9080/TCP 23m
productpagetmp NodePort 10.233.47.14 9080:30258/TCP 77s
ratings ClusterIP 10.233.7.6 9080/TCP 36m
reviews ClusterIP 10.233.58.219 9080/TCP 23m
```
然后在页面中访问 30258 端口(大家的端口不一样,按自己的来)。

接着打开 `http://192.168.3.150:30258/productpage?u=normal`。
因为当前使用 Service 绑定 Pod,**因此会使用轮询实现负载均衡**,你可以多次刷新 `http://192.168.3.150:30258/productpage?u=normal`,会查到右侧的评分星星有所变化。
> Istio 默认情况下使用轮询负载均衡的方法。
页面右侧评论显示规则是 无星星 => 黑色星星 => 红色星星。

### 部署入口网关
#### 什么是 Gateway
终于来到体验 Istio 的时刻了,在本小节中,我们将会为 productpage 创建 Istio Gateway,对外提供网页访问。
在第二章中,我们已经部署了 istio-ingressgateway,这个组件起到了类似 nginx、apisix 的效果,对外提供端口访问,然后将流量转发到内部服务中。
但是 istio-ingressgateway 并不能直接转发流量到 Pod,它还需要进行一些配置。我们要为 productpage 创建一个站点,绑定对应的域名,这样外部访问 istio-ingressgateway 的端口时,istio-ingressgateway 才知道该将流量转发给谁。在 Istio 中,**定义这种绑定关系的资源叫 Gateway**。
> 后面的章节会解释清楚,这里大概了解即可。

Gateway 类似 Nginx 需要创建一个反向代理时需要绑定的域名配置。
#### 部署 Gateway
创建一个 Gateway,绑定域名入口。
`ingress_gateway.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
```
> `hosts` 表示对外开放的访问路径,你可以绑定域名、IP 等。这里使用 `*` ,表示所有访问都可以进入此网关。
```bash
kubectl -n bookinfo apply -f ingress_gateway.yaml
```
这一步就像 nginx 的监听配置:
```nginx
server {
listen 80;
server_name example.org www.example.org;
#...
}
```
当我们创建 Istio Gateway 之后,istio-ingressgateway 会为我们监控流量,检测不同的域名或端口属于哪个 Istio Gateway 。

### 部署 VirtualService
#### 什么是 VirtualService
虽然创建了 Istio Gateway,但是我们还不能直接通过网关访问到前面部署的微服务,我们还需要创建 Istio VirtualService 将 Istio Gateway 跟对应的 Kubernetes Service 绑定起来,然后流量才能正式流向 Pod。

> **请一定要注意这里,流量实际并不会经过 Service 中,但是 VirtualService 需要通过 Service 来发现 Pod。**
这里类似 nginx 配置反向代理,配置监听之后,还需要指向将请求映射到哪个地址。
```nginx
server {
listen 80;
server_name example.org www.example.org;
#...
}
location /some/path/ {
proxy_pass http://A:9080;
}
```
为什么不直接将 Gateway 跟 Service 绑定,而是中间加个 VirtualService 呢?有句话叫做,计算机领域中的问题,都可以通过增加一个层来解决。
VirtualService 的主要目标是为服务提供稳定的入口地址,并通过配置一系列的路由规则来控制流量在网格内的行为。
就以最简单的路由区配来说,Kubernetes Service 是不支持路由规则的,而 Istio 可以通过指定路由后缀中;Service 不支持流量分析,负载均衡只有轮询。而 Istio 利用 Service 来发现 Pod,然后直接将流量转发到 Pod 中,可以实现各种功能。
VirtualService 可以用于实现以下功能:
请求路由:将请求路由到特定的服务或版本,例如将请求分发到不同版本的服务,以实现灰度发布或金丝雀发布。
请求重试:为失败的请求配置重试策略,以提高服务的可用性。
请求超时:设置请求的超时时间,以便在特定时间内没有得到响应时中断请求。
请求镜像:将请求的副本发送到另一个服务,用于测试新版本的服务,而不影响实际的生产流量。
流量分割:将流量按照特定的比例分发到不同的服务或版本,以实现流量控制。
#### 部署 VirtualService
`productpage_vs.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- bookinfo-gateway
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage
port:
number: 9080
```
```bash
kubectl -n bookinfo apply -f productpage_vs.yaml
```
> 关于 VistualService 中各种配置的作用,在 4.1 章节中会有介绍。
>
>
这里的 YAML 分为两大部分,第一部分是 `http.match`,表示暴露了哪些 API 地址,外部访问时只能访问到这些地址。

可以通过 `http.match` 限制集群外部访问此地址时所能使用的 URL。
然后通过 `http.route` 绑定 Kubernetes Service ,通过 Service 中的服务发现,将流量转发到对应的 Pod 中。

> host 这里,由于 VirtualService 跟 Service/Pod 在同一个命名空间中,所以只需要配置 Service 的名称即可,如果要跨命名空间访问,则需要加上完整的命名空间名称。
#### 什么是 DestinationRule
在本章中,会提前预告 DestinationRule,下一章才会使用 DestinationRule,这里我们知道还有 DestinationRule 这个东西即可。
Istio VistualService 中可以限制外部能够访问的路由地址,而 DestinationRule 则可以配置访问的 Pod 策略。可以为 Istio VistualService 绑定一个 Istio DestinationRule,通过 DestinationRule 我们还可以定义版本子集等,通过更加丰富的策略转发流量。

> 由于只暴露了五个地址,所以外部直接访问 `/` ,是打不开页面的。
#### 检查
为了确保网关没问题,我们需要执行 Istio 命令查看日志:
```bash
istioctl analyze
```
然后我们查看为 productpage 创建的网关。
```bash
root@k8smain:/data/learn/book# kubectl get gw -A
NAMESPACE NAME AGE
bookinfo bookinfo-gateway 26m
```
> Kubernetes 本身也有一个 Gateway,因此不能使用 `kubectl get gateway` 来获取 Istio 的 Gateway,而是使用简写 `gw`。
然后查看 VistualService。
```
root@k8smain:/data/learn/book# kubectl get vs -A
NAMESPACE NAME GATEWAYS HOSTS AGE
bookinfo bookinfo ["bookinfo-gateway"] ["*"] 79m
```
在第二章中,我们通过 Helm 部署了 istio-ingressgateway,其访问端口如下:

在本节部署 bookinfo-gateway 的时候,我们使用了端口 80,因此不需要另外配置 ,直接通过 istio-ingressgateway 的 32309 端口访问即可。
> 访问时一定需要带 `/productpage` ,因为我们并没有放通 `/`。

### 尝试修改 Gateway 端口
如果需要更换端口,可以修改 istio-ingressgateway 的 Service,增加新的端口映射。
```bash
kubectl edit svc istio-ingressgateway -n istio-system
```

然后修改前面的 `ingress_gateway.yaml`,将端口从 80 改成 666 。

即可通过 32666 端口访问到此微服务。
示例:http://192.168.3.150:30666/productpage

--------------------------------
标题:可观测性
来源:/zh/istio/3.1.tlm
## 可观测性
Istio 集成了 Jaeger、Zipkin 和 Skywalking 等链路追踪应用,能够有效地捕获服务网格的结构,展示网络拓扑结构,并分析网格的健康状况。
这一切都得益于 Envoy 代理的实现。由于所有进出流量都需要经过 Envoy 代理,Envoy 可以捕获这些流量记录,并将其推送到相应的链路追踪系统中。这样一来,可以链路追踪系统轻松地监控和分析服务网格内的流量情况。
> 另外 Istio 还支持 Prometheus、 Grafana 收集指标数据。
下面我们将使用官方的模板部署 Kiali 、 还有 Jaeger,然后通过 Kiali 统一查看集群的指标信息。
Kiali 界面示例:

拉取 Istio 官方的仓库:
```bash
git clone https://github.com/istio/istio.git
```
在 `samples/addons/` 目录中有以下目录或文件:
```bash
samples/addons/
├── extras
│ ├── prometheus-operator.yaml
│ ├── prometheus_vm_tls.yaml
│ ├── prometheus_vm.yaml
│ ├── skywalking.yaml
│ └── zipkin.yaml
├── grafana.yaml
├── jaeger.yaml
├── kiali.yaml
├── prometheus.yaml
└── README.md
```
我们启用 `grafana.yaml`、`jaeger.yaml`、`kiali.yaml`、`prometheus.yaml` 四个文件。
```
kubectl apply -f samples/addons
```
> 这些服务默认安装在 istio-system 命名空间下,因此不需要自行设置。
>
> Istio 默认使用 Jaeger 做链路追踪,我们也可以使用 Skywalking 来做追踪。extras 目录中的配置我们可以自行部署。
执行命令查看其 Service 对应的 IP 和端口:
```
kubectl get svc -n istio-system
```

现在,我们有两种方式让 kiali 在外部访问,一种是修改 Service 配置,将其访问类型修改为 NodePort,另一种是使用 istio-ingressgateway 配置流量入口。
> 第二种方式比较麻烦,但是为了验证我们的学习成果,我们不妨使用 Gateway 的方式暴露服务。
#### 通过 Gateway 访问 Kiali
首先,创建一个 Gateway 。
`kiali_gateway.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: kiali-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 15029
name: http-kiali
protocol: HTTP
hosts:
- "*"
```
```bash
kubectl -n istio-system apply -f kiali_gateway.yaml
```
接下来,创建一个 VirtualService 资源,将 Gateway 路由到 Kiali 服务.
`kiali_vs.yaml `
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: kiali
spec:
hosts:
- "*"
gateways:
- kiali-gateway
http:
- match:
- uri:
prefix: /kiali
route:
- destination:
host: kiali.istio-system.svc.cluster.local
port:
number: 20001
```
```bash
kubectl -n istio-system apply -f kiali_vs.yaml
```
然后修改 istio-ingressgateway,新增加一个配置为 kiali 暴露服务。
```
kubectl edit svc istio-ingressgateway -n istio-system
```
```
- name: kiali
nodePort: 32667
port: 15029
protocol: TCP
targetPort: 15029
```
然后访问:http://192.168.3.150:32667/kiali

### 查看链路追踪数据
现在我们在 Shell 执行命令轮询一段时间前面部署的微服务,以便给集群创造访问流量。
```
for i in `seq 1 1000`; do curl -s -o /dev/null http://192.168.3.150:30666/productpage; done
```

> 因为默认链路追踪采样率是 1%,所以可以将请求次数设置大一些。
最终会得到一张类似的图片。



Kiali 的 Graph 数据主要来自两个来源:Prometheus 和 Istio 本身的遥测数据。
**Prometheus**:Prometheus 是一个开源监控和警报工具,它用于收集和存储 Istio 服务网格中的指标数据。Istio 使用 Envoy 代理收集遥测数据,这些数据随后被 Prometheus 抓取和存储。Kiali 使用这些 Prometheus 数据来生成服务之间的流量、错误率、延迟等指标。
**Istio 遥测数据**:Istio 服务网格生成的遥测数据包括请求、响应、延迟以及 Envoy 代理的其他性能指标。这些数据由 Istio 组件(例如 Mixer 和 Pilot)以及 Envoy 代理本身生成。Kiali 从这些遥测数据中获取服务拓扑信息,以创建服务之间的依赖关系图。
Kiali 将这两个数据源的信息整合在一起,生成 Graph,它展示了服务网格的拓扑结构、服务之间的流量以及其他性能指标。这有助于用户更好地理解服务之间的依赖关系,发现潜在的性能问题,并优化服务网格配置。
#### 可能失败的原因
如果你的 Kiali 一直显示 Empty Graph。请关注以下几种可能的情况:
* 集群版本低于 1.23 ,需要升级 Kubernetes 集群。
* 安装了 Kubesphere,说多了都是泪,Kubesphere 太重了,笔者花了一晚上时间重新安装集群。
* 访问的地址不正确,没有配置对 `/productpage` 的访问地址,请求流量没有打入集群。
* Pod 没有被注入 istio-proxy。
你可以在 Kiali 的 `Workloads` 查看每个负载的 Pod 信息,正常情况应当如下所示:

#### 修复 Kiali Grafana 问题
点击右上角的消息,可能会提示配置不正确,因为 kiali 需要从 Grafana 拉取数据。

编辑 configmap 。
```
kubectl edit configmap kiali -n istio-system
```
在里面添加如下两行内容。
```yaml
grafana: \n enabled: true \n url: \"http://grafana.istio-system.svc.cluster.local:3000\"
\ \n in_cluster_url: \"http://grafana.istio-system.svc.cluster.local:3000\"\n
```

如果使用的是可视化工具,添加就简单了。
```yaml
grafana:
enabled: true
url: "http://grafana.istio-system.svc.cluster.local:3000"
in_cluster_url: "http://grafana.istio-system>.svc.cluster.local:3000"
```

然后使用 `kubectl describe configmap kiali -n istio-system` 查看配置是否正确。
--------------------------------
标题:流量管理
来源:/zh/istio/4.traffic
# 4, 流量管理
主要演示了使用 Istio Gateway、VirtualService 对外暴露服务的访问地址 ,以及基于 Istio 实现可观察性的 Kiali 组件。让我们回在上一章中部署的 bookinfo 示例已经学习了什么:
* 使用 Istio Gateway 创建 “站点”;
* 使用 Istio VistualService 暴露 Kubernetes Service,并指定暴露的路由后缀。
* 使用 Kiali 收集服务间的指标。
通过快速练习,我们学到了如何在 Istio 中暴露服务,以及只暴露部分 API。可是只暴露服务并没有太大的用处,因为市面上各种网关都可以做到,并且功能更加丰富。
在微服务系统中,我们会碰到很多关于服务治理的问题,下面是笔者从 ChatGPT 中获取到的一些关于服务治理常见的问题。
1. 服务发现:在动态的微服务环境中,如何实时地发现和注册新的服务实例?
2. 负载均衡:如何在服务实例之间有效地分配请求流量,以实现高性能和高可用性?
3. 容错处理:如何处理服务之间的故障,例如服务实例故障、网络故障等?
4. 流量管理:如何控制服务间的请求流量,例如请求路由、流量分割、金丝雀发布等?
5. 服务监控:如何实时地监控服务的性能和健康状况?
6. 链路追踪:如何跟踪和分析分布式系统中的请求调用链?
7. 安全性:如何确保服务之间的通信安全,例如身份验证、授权和加密?
8. 策略执行:如何实施和管理服务治理的策略,例如限流、熔断、访问控制等?
9. 配置管理:如何在服务之间统一和动态地管理配置信息?
10. 服务编排:如何协调服务之间的交互,以实现复杂的业务流程?
前面的章节提到过, Istio 是服务治理的工具。所以,在本章中,将会介绍 Istio 的流量管理能力,来解决微服务中关于服务治理的部分问题。
Istio 的流量管理模型源于和服务一起部署的 Envoy,网格内 Pod 中的应用发送和接收的所有流量(data plane流量)都经由 Envoy,而应用本身不需要对服务做任何的更改,这对业务来说是非侵入式的,却可以实现强大的流量管理。

【不要点了,这只是一张图片】
### 基于版本的路由配置
在第三章访问的 http://192.168.3.150:30666/productpage?u=normal 地址中,我们每次刷新得到的结果都不一样。
因为 Kubenetes Service 通过标签中的 `app: reviews` 来绑定对应的 Pod,正常情况下,Kubernetes 会将客户端的请求以轮询的方式转发到 Deployment 中的 Pod,VirtualService 也是如此。

```bash
selector:
app: reviews
```
三个不同的 Reviews Deployment 都带有相同的 `app: reviews` 标签,所以 Service 会把它们的 Pod 放到一起,VirtualService 会将流量以轮询的方式分发到每个版本中。
```
labels:
app: reviews
version: v1
labels:
app: reviews
version: v2
labels:
app: reviews
version: v3
```
所以,流量进入 Reviews VirtualService 之后,会被 Kubernetes 均衡地分配到各个 Pod 中。接下来我们将会使用按照版本的形式将流量划分到不同的版本服务中。
Istio 通过 DestinationRule 定义了应用的版本,使用 Istio DestinationRule 设置 reviews v1/v2/v3 版本的定义如下所示:
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
```
> 看看就行,不用执行命令。
这看起来非常简单, DestinationRule 没有什么特别的配置。通过 `name: v1` 定义版本,通过 `labels` 指定哪些符合条件的 Pod 划分到这个版本中。
接下来我们创建一个 yaml 文件,给书店微服务的四个应用都创建一个 DestinationRule 。
`service_version.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: productpage
spec:
host: productpage
subsets:
- name: v1
labels:
version: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: ratings
spec:
host: ratings
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v2-mysql
labels:
version: v2-mysql
- name: v2-mysql-vm
labels:
version: v2-mysql-vm
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: details
spec:
host: details
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
```
```bash
kubectl -n bookinfo apply -f service_version.yaml
```
执行命令查询更多信息
```bash
$> kubectl get destinationrules -o wide -n bookinfo
NAME HOST AGE
details details 59s
productpage productpage 60s
ratings ratings 59s
reviews reviews 59s
```
接着我们为三个微服务 productpage、ratings、details 定义 Istio VirtualService,因为它们都只有 v1 版本,所以在 VirtualService 中直接将流量转发的 v1 版本即可。
`3vs.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: productpage
spec:
hosts:
- productpage
http:
- route:
- destination:
host: productpage
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- route:
- destination:
host: ratings
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: details
spec:
hosts:
- details
http:
- route:
- destination:
host: details
subset: v1
---
```
```bash
kubectl -n bookinfo apply -f 3vs.yaml
```
` host: reviews` 使用 Service 的名称。如果这样填写,该规则只能应用于当前命名空间的 Service,如果需要将流量引导到其它命名空间的 Service,则需要使用完整的 DNS 路径,如:`reviews.bookinfo.svc.cluster.local`。
而对于 reviews 服务,我们在 VirtualService 只将流量转发到 v1 版本,忽略 v2、v3。
`reviews_v1_vs.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
```
```bash
kubectl -n bookinfo apply -f reviews_v1_vs.yaml
```
然后我们查看所有的 VirtualService 。
```bash
$> kubectl get vs -n bookinfo
NAME GATEWAYS HOSTS AGE
bookinfo ["bookinfo-gateway"] ["*"] 76m
details ["details.bookinfo.svc.local"] 103m
productpage ["productpage.bookinfo.svc.local"] 103m
ratings ["ratings.bookinfo.svc.local"] 103m
reviews ["reviews"] 103m
```

之后,无论怎么刷新 http://192.168.3.150:32666/productpage ,右侧的 Book Reviews 都不会显示星星,因为流量都转发到 v1 版本中,而 v1 版本是不会有星星的。

Istio 起作用的原理大概是这样的,首先是 istio-ingressgateway 将流量转发到 bookinfo 网关中,然后 productpage VirtualService 根据对应的路由规则,判断是否放通流量,最后转发到对应的 productpage 应用中。接着 productpage 需要访问其它服务例如 reviews,发出的请求会经过 Envoy,Envoy 根据配置的 VirtualService 规则,直接将流量转发到对应的 Pod 中。

### 基于 Http header 的路由配置
基于 Http header 的转发,是通过 HTTP 请求中的 header 值,将流量转发到对应的 Pod 中。
在本节中,我们将会通过配置 DestinationRule ,将 header 带有 `end-user: jason` 的流量转发到 v2 中,其它情况依然转发到 v1 版本。
将 reviews 的 DestinationRule 描述文件的内容改成:
```yaml
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
```
完整的 YAML如下:
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
```
> kubectl -n bookinfo apply -f reviews_v2_vs.yaml

然后在页面中的右上角点击 `Sign in` 进行登录,账号密码都是 jason。

此时 Book Reviews 一直显示星星。

如果我们查看 productpage 的日志:

productpage 将这个 header 头转发给 `http://reviews:9080/` ,然后流量经过 Envoy 时,Envoy 检测到 Http header 中带有 end-user ,通过规则决定将流量转发到 reviews v2。**在这个过程中并不需要 Service 参与**。
- 经过上面的配置,下面是请求的流程:
- `productpage` → `reviews:v2` → `ratings` (针对 `jason` 用户)
- `productpage` → `reviews:v1` (其他用户)
当然,我们也可以通过 URL 的方式划分流量,例如 `/v1/productpage` 、`/v2/productpage` 等,在本章中,就不再赘述这些了,读者可以从官方文档中了解更多。
### 故障注入
故障注入是 Istio 模拟故障的一种手段,通过故障注入我们可以模拟一个服务出现故障的情况,然后从实际请求中看到出现故障时,整个微服务是否会乱套。通过故意在服务间通信中引入错误,例如延迟、中断或错误的返回值,可以**测试系统在不理想的运行状况下的表现**。这有助于发现潜在的问题,提高系统的健壮性和可靠性。
将前面部署的 ratings 的 VirtualService,改造一下。
`ratings_delay.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- match:
- headers:
end-user:
exact: jason
fault:
delay:
percentage:
value: 100.0
fixedDelay: 7s
route:
- destination:
host: ratings
subset: v1
- route:
- destination:
host: ratings
subset: v1
```
```
kubectl -n bookinfo apply -f ratings_delay.yaml
```
再次访问网页,发现评论区已经加载不出来了,因为超时。

#### 两种故障注入
在 Istio 的 VirtualService 中,`fault` 配置用于注入故障,以模拟和测试应用程序在出现问题时的行为。主要有两种类型的故障注入:延迟(delay)和异常(abort)。
**延迟故障注入**
延迟故障注入用于在应答之前向请求添加指定的延迟时间。这可以测试应用程序在网络延迟或服务响应缓慢的情况下的表现。以下是一个示例,演示了如何在 VirtualService 中添加一个延迟故障注入:
```yaml
http:
- fault:
delay:
percentage:
value: 100.0
fixedDelay: 5s
```

延迟(delay)故障注入有两个主要属性。
- `percentage`: 表示注入延迟的概率,取值范围为 0.0 到 100.0。例如,50.0 表示有 50% 的概率注入延迟。
- `fixedDelay`: 表示注入的固定延迟时间,通常以秒(s)或毫秒(ms)为单位。例如,`5s` 表示 5 秒延迟。
延迟故障注入的示例:
```yaml
fault:
delay:
percentage:
value: 50.0
fixedDelay: 5s
```
在这个示例中,`delay` 配置了一个 50% 概率发生的 5 秒固定延迟。
**异常故障注入**
异常故障注入用于模拟请求失败的情况,例如 HTTP 错误状态码或 gRPC 状态码。这可以帮助测试应用程序在遇到故障时的恢复能力。以下是一个示例,演示了如何在 VirtualService 中添加一个异常故障注入:
```
http:
- fault:
abort:
percentage:
value: 100.0
httpStatus: 503
```
也可以将延迟故障注入 和 异常故障注入两者放在一起同时使用。
```yaml
http:
- fault:
delay:
percentage:
value: 50.0
fixedDelay: 5s
abort:
percentage:
value: 50.0
httpStatus: 503
```
> 虽然放在一起使用,但是并不会两种情况同时发生,而是通过 percentage 来配置出现的概率。
异常(abort)故障注入有四个主要属性。
- `percentage`: 表示注入异常的概率,取值范围为 0.0 到 100.0。例如,50.0 表示有 50% 的概率注入异常。
- `httpStatus`: 表示要注入的 HTTP 错误状态码。例如,`503` 表示 HTTP 503 错误。
- `grpcStatus`: 表示要注入的 gRPC 错误状态码。例如,`UNAVAILABLE` 表示 gRPC 服务不可用错误。
- `http2Error`: 表示要注入的 HTTP/2 错误。例如,`CANCEL` 表示 HTTP/2 流被取消。
异常故障注入的示例:
```yaml
fault:
abort:
percentage:
value: 50.0
httpStatus: 503
```
实验完成后,别忘了将 ratings 服务恢复正常。
```
kubectl -n bookinfo apply -f 3vs.yaml
```
### 比例分配流量
使用下面的配置,可以把 50% 的流量分配给 `reviews:v1` 和 `reviews:v3`:
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 50
- destination:
host: reviews
subset: v3
weight: 50
```
刷新浏览器中的 `/productpage` 页面,大约有 50% 的几率会看到页面中带 *红色* 星级的评价内容。这是因为 `reviews` 的 `v3` 版本可以访问带星级评价,但 `v1` 版本不能。
### 请求超时
不同编程语言都会提供 http client 类库,程序发起 http 请求时,程序本身一般都会有超时时间,超过这个时间,代码会抛出异常。例如网关如 nginx、apisix 等,也有 http 连接超时的功能。
在 Istio 中,服务间的调用由 Istio 进行管理,可以设置超时断开。
我们可以为 reviews 服务设置 http 入口超时时间,当其它服务 请求reviews 服务时,如果 http 请求超过 0.5s,那么 Istio 立即断开 http 请求。
`reviews_timeout.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
timeout: 0.5s
```
```
kubectl -n bookinfo apply -f reviews_timeout.yaml
```
因为 reviews 依赖于 ratings 服务,为了模拟这种超时情况,我们可以给 ratings 注入延迟故障。这样 ratings 会给所有请求都延迟 2s 才会返回响应,但是 reviews 要求所有请求 reviews 的流量在 0.5s 内响应。
给 ratings 设置延迟故障:
```yaml
kubectl -n bookinfo apply -f - < 注:因为 productpage 是 Python 编写的,其代码中设置了请求失败后自动重试一次,因此页面刷新后 1 s 后才会完成,而不是 0.5s。
还有一点关于 Istio 中超时控制方面的补充说明,除了像本文一样在路由规则中进行超时设置之外,还可以进行请求一级的设置,只需在应用的对外请求中加入 `x-envoy-upstream-rq-timeout-ms` 请求头即可。在这个请求头中的超时设置单位是毫秒而不是秒。
现在让我们将本小节的故障清理掉,恢复正常的微服务。
```yaml
kubectl -n bookinfo apply -f - < 这里使用的 NodePort 只是为了分别预览访问,后续还需要通过 Gateway 来实验熔断。
然后查看 Service 列表。

通过浏览器打开对应的服务。

接着给 httpbin 创建一个 DestinationRule ,里面配置了熔断规则。
`httpbin_circurt.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: httpbin
spec:
host: httpbin
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100
```
```
kubectl -n bookinfo apply -f httpbin_circuit.yaml
```
`DestinationRule`(目标规则)用于定义访问特定服务的流量策略。`DestinationRule` 配置中的 `trafficPolicy` 属性允许为服务指定全局的流量策略,这些策略包括负载均衡设置、连接池设置、异常检测等。
另外,我们在创建熔断时也可以设置重试次数。
```
retries:
attempts: 3
perTryTimeout: 1s
retryOn: 5xx
```
#### 创建访问者服务
在 Istio 服务网格环境下,流量进入网格后会被 Envoy 拦截,接着根据相应的配置实现路由,熔断也是在 Envoy 之间实现的,只有流量经过 Envoy ,才会触发 Istio 的熔断机制。
上一小节中我们部署了 httpbin 应用, 但是熔断是服务之间通讯出现的,所以我们还需要部署一个服务请求 httpbin,才能观察到熔断过程。Istio 官方推荐使用 fortio 。
部署 fortio 的 YAML 如下:
`fortio_deploy.yaml`
```yaml
apiVersion: v1
kind: Service
metadata:
name: fortio
labels:
app: fortio
service: fortio
spec:
ports:
- port: 8080
name: http
selector:
app: fortio
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fortio-deploy
spec:
replicas: 1
selector:
matchLabels:
app: fortio
template:
metadata:
annotations:
# This annotation causes Envoy to serve cluster.outbound statistics via 15000/stats
# in addition to the stats normally served by Istio. The Circuit Breaking example task
# gives an example of inspecting Envoy stats via proxy config.
proxy.istio.io/config: |-
proxyStatsMatcher:
inclusionPrefixes:
- "cluster.outbound"
- "cluster_manager"
- "listener_manager"
- "server"
- "cluster.xds-grpc"
labels:
app: fortio
spec:
containers:
- name: fortio
image: fortio/fortio:latest_release
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http-fortio
- containerPort: 8079
name: grpc-ping
```
```
kubectl -n bookinfo apply -f fortio_deploy.yaml
```
部署 fortio 之后,我们进入到 fortio 容器中,执行命令请求 httpbin。
执行命令获取 fortio 的 Pod 名称:
```bash
export FORTIO_POD=$(kubectl get pods -n bookinfo -l app=fortio -o 'jsonpath={.items[0].metadata.name}')
```
然后让 Pod 容器执行命令:
```bash
kubectl -n bookinfo exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio curl -quiet http://httpbin:8000/get
```

如果上面的命令执行没问题的话,我们可以通过下面的命令对 httpbin 服务进行大量请求,并且分析请求统计结果。
```bash
kubectl -n bookinfo exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
```

在控制台中可以看到请求返回 200 和 503 的比例。

#### 创建 productpage 熔断
在前面的小节中,我们使用了 httpbin 进行熔断实验,当然我们也可以给那些暴露到集群外的应用创建熔断。
这里我们继续使用之前的 bookinfo 微服务的 productpage 应用。
给 productpage 创建一个熔断规则:
`productpage_circuit.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: productpage
spec:
host: productpage
subsets:
- name: v1
labels:
version: v1
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100
```
```bash
kubectl -n bookinfo apply -f productpage_circuit.yaml
```
然后我们使用 fortio 测试 productpage 应用,从 istio gateway 入口进行访问。
```
kubectl -n bookinfo exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 20 -loglevel Warning http://192.168.3.150:32666/productpage
```


然后删除 productpage 的熔断配置,重新恢复成一个正常的应用。
```yaml
kubectl -n bookinfo apply -f - < 强调过很多次,流量实际上不会到达 Service。
这里的 Service 为 Envoy 提供了一些关于 Pod 的基础信息,VirtualService 会在 Service 的基础上**增强流量管理和控制功能**。

虽然 Istio 使用 Envoy 管理流量,但 Kubernetes 的 Service 仍然在 Istio 中发挥作用。Service 用于定义服务的基本属性,例如服务的名称和端口。Istio 使用这些信息从 Kubernetes API 服务器获取服务的端点,并将这些信息传递给 Envoy 。这样,Envoy 就可以知道如何路由到其他服务。
**功能差异**
Kubernetes 的 Service 提供了基本的负载均衡和服务发现功能,而 Istio 的 VirtualService 提供了更丰富的流量管理能力,如按权重分配流量、请求重试、故障注入、流量镜像等。
**兼容性**
Istio 可以与 Kubernetes 集群无缝集成,VirtualService 和 Service 可共同工作以实现更强大的服务治理功能。Istio 不仅支持 Kubernetes,还可以与其他平台(如 VM、Consul 等)一起使用。
总之,Istio 的 VirtualService 和 Kubernetes 的 Service 是相辅相成的,它们共同为服务提供了更强大的流量管理和控制功能。在使用 Istio 时,通常需要将 VirtualService 与 Kubernetes 的 Service 结合使用,以实现所需的服务治理目标。
### VirtualService 和 DestinationRule 的关系
在 Istio 中,VirtualService 和 DestinationRule 是两个关键的自定义资源定义(CRD),它们用于配置和控制服务间的流量路由。
它们之间的关系可以概括为:VirtualService 定义了流量的路由规则,而 DestinationRule 定义了**流量到达目的地后如何进行负载分发和连接池管理**。
**VirtualService 用于定义流量的路由规则。**当请求从一个服务到另一个服务时,VirtualService 可以指定如何将流量路由到不同的目的地(例如,不同的服务实例,版本或子集)。VirtualService 还可以根据请求的属性(如请求头、路径、来源等)对流量进行匹配和分发。此外,VirtualService 可以配置复杂的路由行为,如重试、超时和故障注入等。
**DestinationRule 被用于控制流量的分发和连接池管理。**DestinationRule 定义了服务的子集(即服务的不同版本或变体),并指定如何根据负载均衡策略(如轮询、随机、最少连接等)将流量分发到这些子集。此外,DestinationRule 还可以配置连接池设置(如最大连接数、空闲超时等)和传输层安全策略(如 TLS 设置)。
总之,VirtualService 和 DestinationRule 在 Istio 中共同实现了流量的精细控制。VirtualService 用于定义流量的路由规则,而 DestinationRule 则负责处理流量到达目的地后的负载分发和连接池管理。
### VirtualService 的定义
VirtualService 的 spec 一共有五大类属性。
```yaml
spec:
hosts:
gateways:
http:
tls:
tcp:
```
`hosts`:这是一个字符串列表,用于指定 VirtualService 应用的目标主机。这些主机可以是 Kubernetes Service 名称,也可以是外部服务的域名。流量将根据这些主机进行路由。
```yaml
hosts:
- my-service.example.com
```
`gateways`:这是一个字符串列表,用于指定 VirtualService 应用的网关。Istio 网关用于配置进出网格的流量。如果省略此字段,默认情况下,VirtualService 仅适用于网格内部的流量。
```yaml
gateways:
- my-gateway
```
`http`:此属性包含一个 HTTPRoute 列表,用于定义 HTTP 流量的路由规则。每个 HTTPRoute 可以包含匹配条件、路由目标、重试、超时等配置。
> `http` 属性是 VirtualService `spec` 中的一个字段,它包含一个 HTTPRoute 列表,用于定义 HTTP 流量的路由规则。HTTPRoute 包含以下主要属性:
>
>
>
> `match`:此属性包含一个 HTTPMatchRequest 列表,用于定义流量匹配条件。每个 HTTPMatchRequest 可以包含以下匹配条件:
>
> - `uri`:请求 URI 的匹配条件,可以是前缀匹配、精确匹配或正则表达式匹配。
> - `method`:请求方法(如 GET、POST 等)的匹配条件。
> - `headers`:请求头的匹配条件,可以是前缀匹配、精确匹配或正则表达式匹配。
> - `queryParams`:查询参数的匹配条件,可以是前缀匹配、精确匹配或正则表达式匹配。
> - `sourceLabels`:流量来源的 Pod 标签匹配条件。
> - `gateways`:流量来源的网关列表。
>
>
>
> `route`:此属性包含一个 HTTPRouteDestination 列表,用于定义流量的路由目标。每个 HTTPRouteDestination 包含以下属性:
>
> - `destination`:流量的目的地,包括 `host`(目标主机名)、`subset`(目标服务子集)和 `port`(目标端口)。
> - `weight`:流量分发到此目的地的权重。所有路由目标的权重总和应为 100。
>
>
>
> `redirect`:此属性用于配置 HTTP 重定向。可以指定重定向的 URI、Authority 和状态码。
>
>
>
> `rewrite`:此属性用于配置 URI 和 Authority 的重写规则。
>
>
>
> `timeout`:此属性用于配置请求的超时时间。
>
>
>
> `retries`:此属性用于配置重试策略,包括尝试次数、每次尝试的超时时间和可重试的状态码。
>
>
>
> `fault`:此属性用于配置故障注入,包括延迟注入和异常注入。这对于测试和模拟故障场景非常有用。
>
>
>
> `mirror`:此属性用于配置流量镜像目的地。流量镜像允许将流量复制到另一个服务,用于观察和测试。
>
>
>
> `corsPolicy`:此属性用于配置 CORS 策略,包括允许的来源、允许的方法、允许的头部等。
>
>
>
> `headers`:此属性用于配置请求和响应头的操作,包括添加、修改和删除头部。
`tls`:此属性包含一个 TLSRoute 列表,用于定义基于 SNI 的 TLS 流量的路由规则。每个 TLSRoute 可以包含匹配条件和路由目标。
`tcp`:此属性包含一个 TCPRoute 列表,用于定义 TCP 流量的路由规则。每个 TCPRoute 可以包含匹配条件和路由目标。
下面是一个 VirtualService 示例。
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: my-virtual-service
spec:
hosts:
- my-service.example.com
gateways:
- my-gateway
http:
- match:
- uri:
prefix: /api/v1
route:
- destination:
host: my-service-v1
weight: 90
- destination:
host: my-service-v2
weight: 10
retries:
attempts: 3
perTryTimeout: 2s
timeout: 10s
- route:
- destination:
host: my-service-v1
```
### DestinationRule 的定义
DestinationRule 的 spec 中一共有三大类属性。
```
spec:
host: my-service
trafficPolicy:
subsets:
```
`host`:此属性是一个字符串,用于指定目标主机名。它可以是 Kubernetes Service 名称,也可以是外部服务的域名。
`trafficPolicy`:此属性用于配置全局的流量策略,包括负载均衡策略、连接池设置和传输层安全策略。这些设置将应用于所有子集(除非子集中明确覆盖)。
`subsets`:此属性包含一个 Subset 列表,用于定义服务的子集(即服务的不同版本或变体)。每个 Subset 包含以下属性:
- `name`:子集的名称。
- `labels`:子集的标签选择器。这些标签用于选择对应子集的 Kubernetes Pod。
- `trafficPolicy`:子集的流量策略。这些设置将覆盖全局的 `trafficPolicy`。
下面是一个示例:
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: my-destination-rule
spec:
host: my-service
trafficPolicy:
loadBalancer:
simple: LEAST_CONN
connectionPool:
tcp:
maxConnections: 100
http:
http2MaxRequests: 1000
tls:
mode: ISTIO_MUTUAL
subsets:
- name: v1
labels:
version: v1
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
- name: v2
labels:
version: v2
```
--------------------------------
标题:出入口网关
来源:/zh/istio/5.gateway
# 5,出入口网关
Istio 可以管理集群的出入口流量,当客户端访问集群内的应用时, Istio 可以将经过 istio-ingressgateway 的流量实现负载均衡和熔断等一系列功能。
可是,如果集群内的一个应用要访问 google.com ,那么我们可以给内部所有请求了 google.com 的流量设置负载均衡吗?答案是可以,Istio 提供了 istio-egressgateway 实现这种功能。因为 Pod 中的容器要访问网络时,会被 Envoy 拦截,Envoy 可以很容易地分析这些请求,然后通过一系列手段影响着请求的行为。
在本章中,将会简单说一下 istio-ingressgateway 和 istio-egressgateway。
## istio-ingressgateway
入口网关指的是从外部经过 istio-ingressgateway 流入集群的流量,需要创建 Gateway 绑定流量。
关于 istio-ingressgateway 经过前面几章的学习,大家应该不陌生了。
istio-ingressgateway 由 Pod 和 Service 组成。 istio-ingressgateway 本身就是一个网关应用,你可以把它当作 Nginx、Apisix、Kong ,你可以从各种各种网关应用中找到与 istio-ingressgateway 类似的概念。


作为一个应用,它需要对外开放一些端口,只有当流量经过这些端口时, istio-ingressgateway 才会起作用。为了在 Kubernetes 中暴露端口, istio-ingressgateway 还有一个 Service 对象。

有了 istio-ingressgateway 之后,我们可以通过 Istio Gateway 监控一些域名或IP,然后暴露集群内部的服务 。
Gateway 的概念跟 Nginx 有很多相似之处。
比如从配置上看, Gateway 跟 Nginx 如果要监控某个入口流量,它们的配置如下:
Nginx:
```nginx
server {
listen 80;
server_name example.org www.example.org;
#...
}
```
Gateway:
```yaml
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- example.org
- www.example.org
```
这些配置指定了 Gateway 和 Nginx 只监控哪些流量。
紧接着,监控到指定入口的流量之后,需要将流量转发到集群内的应用中。
Nginx 可以直接在同一个配置文件里面设置:
```nginx
server {
listen 80;
server_name example.org www.example.org;
#...
}
location /some/path/ {
proxy_pass http:/bookinfo:9080/;
}
```
而 Gateway 需要使用 VirtualService 指定流量转发到哪里,并且 VirtualService 还可以进一步筛选入口地址。
```yaml
spec:
hosts:
- "www.example.org"
gateways:
# 绑定 Gateway
- mygateway
http:
route:
- destination:
host: bookinfo
port:
number: 9080
```
所以总结起来,Istio 的做法是 Gateway 监控入口流量,通过 VirtualService 设置**流量进入的策略**,并指向 Service。而 DestinationRule 则定义了**流量流向 Pod 的策略**。
#### 部署服务
下面我们将使用 httpbin 服务作为示例,如何一步步配置在外部访问 httpbin 服务。
首先部署一个 httpbin 服务,这个 httpbin 服务很简单,包含了 Service 和 Deployment 。
`httpbin.yaml`
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
service: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
serviceAccountName: httpbin
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
ports:
- containerPort: 80
```
```bash
kubectl -n bookinfo apply -f httpbin.yaml
```
#### 配置 Gateway
然后创建一个 Gateway ,指定监听哪些入口流量。
`httpbin_gw.yaml`
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: httpbin-gateway
spec:
selector:
istio: ingressgateway # use Istio default gateway implementation
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "httpbin.s1.whuanle.cn"
- "*"
```
> 这一步为了大家能够通过域名更加直观地了解 Gateway,大家可以修改 `httpbin.s1.whuanle.cn` 替换为自己的域名。
>
> 然后在自己的电脑中打开 `C:\Windows\System32\drivers\etc\hosts ` 增加一条记录 ,将 IP 指向自己的服务器。
>
> 
```bash
kubectl -n bookinfo apply -f httpbin_gw.yaml
```
现在,我们已经让 istio-ingressgateway 帮我们关注 httpbin.s1.whuanle.cn 这个地址,如果有人访问了 httpbin.s1.whuanle.cn,那么这个流量将会流入到 httpbin-gateway。

接下来我们将要为 Gateway 配置服务地址,并配置外部允许访问的地址后缀。
配置 VistualService:
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "*"
gateways:
- httpbin-gateway
http:
- match:
- uri:
prefix: /status
- uri:
prefix: /delay
route:
- destination:
port:
number: 8000
host: httpbin
```
> 当 Gateway 和 VirtualService 端口只有一个时,不需要配置端口绑定。
```
kubectl -n bookinfo apply -f httpbin_vs.yaml
```
找到 istio-ingressgateway 对外暴露的端口。
```
kubectl get svc istio-ingressgateway -n istio-system
```

httpbin 是一个 http 测试程序,我们可以通过使用 `/status/{状态码}` 获取对应的 http 请求状态。
例如:



如果我们不希望这个服务被外界访问到,我们可以先把 `/status` 删除。
```yaml
kubectl apply -f - <【图源:互联网】
新版本上线之前,经历过开发和测试人员的验证,也经过产品经理的验收。可是当要上线到生产环境时,谁也保证不了上线一定就能跑起来。所以往往需要在上线时保持新版本和旧版本同时在用,测试人员或内测用户可以访问新版本,其他人继续使用旧版本。再有就是上线时新旧系统能够丝滑切换,用户完全感知不到这种变化。
并且新版本上线时,不应该影响旧版本的运行,要求实现不停止更新。但是除了应用本身,还涉及到新旧版本共有同一个数据库,新旧版本应用对应的数据库表结构不一样。
> 新旧版本切换带来的问题很多,但是,在本系列教程中我们只考虑外部访问效果即可。
很多项目的版本更新做得并不好。很多初级阶段的 Web 服务团队,每次更新都需要中断当前的应用,后端服务有一段时间内不可用。如果新版本有问题,那么还需要重新发布旧版本。如果只是内部服务,问题不大,可是如果系统是给客户使用的,那么很容易让客户给出一个低分的评价。

【图源:互联网】
Kubernetes 中虽然有滚动升级,能够逐渐使用新版本的 Pod 替换旧版本的 Pod,两个版本共存,但是并不能做到流量自由切分,一个流量进入时,依然会在新旧版本的 Pod 中轮询选择。但是我们还是可以调整 v1 和 v2 的 Pod 数量,实现流量按照比例划分,比如 v1 的 Pod 数量有 40 个,v2 的 Pod 的数量有 60 个,那么按照比例,会有 60% 的流量进入到 v2 中。不过这样并没有什么鸟用。
> Kubernetes 滚动升级、伸缩参考资料:https://k8s.whuanle.cn/3.pod/6.scale.html
Istio 中虽然没有名为 金丝雀发布的功能,但是按照之前我们所学到的 Istio 的 VirtualService 、DestinationRule 就可以实现根据请求的 header 等,将流量转发到不同的子版本中,我们使用这些操作方法即可实现金丝雀发布。
### 金丝雀发布
尴尬,在学习 Istio 之前,我对蓝绿发布、灰度发布、金丝雀发布的概念并不清晰,很多文章都是将它们分成三类发布方式。实际上蓝绿发布和金丝雀发布都是灰度发布的一种。
灰度发布是两个版本共存,将一小部分流量切到新版本中,当新版本经过一段时间验证后,如果没有问题,再将流量逐步切换到新版本中。
所以,可以把蓝绿发布、A/B 测试、金丝雀发布都划分为灰度发布。
> 当然,它们有一些差别,每个人的划分方法也有差别,有的说法是金丝雀发布才是灰度发布。
**蓝绿发布**
蓝绿发布的方式是使用另一套资源部署新版本,然后将旧版本的流量都切换到新版本中去,缺点是需要消耗两套资源,而且蓝绿发布是全量切换,如果新版本出现问题则会影响到所有用户。
**A/B 测试**
A/B 测试是同时部署两个版本对等的版本来接收流量,但它不是指新旧版本。比如,产品经理有一个好主意,做新时代的农场区块链,想知道大家喜欢养猪还是养鸡。于是发布了养猪农场和养鸡农场两个对等的应用,然后邀请不同的用户进行内测,喜欢养猪的用户会被导向养猪农场应用。然后收集用户的意见和各种指标数据,分析用户喜欢养什么动物,最后决定上线什么版本。
**金丝雀发布**
先上线一个新版本,然后根据规则将一小部分用户导向到新应用,观察新版本在生产环境的表现,如果达到预期,则逐步将流量切换到新版本中。
### 按照流量比例划分
在第四章中,我们使用 DestinationRule 为 reviews 定义了三个版本,但是每个版本只有一个 Pod。
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
```

现在使用命令将 v1 版本增加到 10 个 pod。
```yaml
kubectl scale deployment reviews-v1 -n bookinfo --replicas=10
```

在原配置不变的情况下,我们来部署 VirtualService,将流量的 90% 打入到包含 10个 Pod 的 v1 版本。因为 v2 版本只有10% 的流量,所以只需要 1 个 Pod 也可以支撑了叭。
```yaml
kubectl -n bookinfo apply -f - < 在 Istio 中,默认情况下,服务之间的通信不会被加密或进行身份验证。比如说, A 服务通过 http 请求 B 服务,流量经过 Envoy A 时,Envoy A 直接将流量发送到 Envoy B 中,流量不会进行加密处理,也就是明文请求。
Istio 的 Peer Authentication 主要解决以下问题:
* 保护服务到服务的通信。
* 提供密钥管理系统,通讯加密需要使用证书,而证书会过期,所以需要一个管理系统自动颁发证书、替换证书等。
* 为每个服务提供强大的身份标识,以实现跨群集和云的互操作性。
**Request Authentication**
Request authentication 用于外部请求的用户认证, Istio 使用 JWT(JSON Web Token) 来验证客户端的请求,并使用自定义认证实现或任何 OpenID Connect 的Request authentication 认证实现来简化的开发人员体验。
支持以下认证类型:
- ORY Hydra
- Keycloak
- Auth0
- Firebase Auth
- Google Auth
### Peer Authentication
Istio 的 PeerAuthentication 是一种安全策略,用于对服务网格内的工作负载之间的通信进行双向 TLS(mTLS)验证。

通过 PeerAuthentication 在 Envoy 间启用 mTLS,以确保工作负载之间的通信在传输过程中是加密和安全的。
> PeerAuthentication 可以配置为整个集群或只在命名空间中起作用,但是只能有一个网格范围的 Peer 认证策略,每个命名空间也只能有一个命名空间范围的 Peer 认证策略。
#### PeerAuthentication 的定义
下面是一个简单的 PeerAuthentication 示例:
```yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: my-peer-authentication
namespace: my-namespace
spec:
selector:
matchLabels:
app: my-app
mtls:
mode: STRICT
```
- `selector`: 标签选择器,用于选择应用 PeerAuthentication 策略的工作负载。例如:
```yaml
selector:
matchLabels:
app: my-app
```
如果省略选择器,PeerAuthentication 策略将应用于命名空间中的所有工作负载。
- mtls: 定义双向 TLS 的模式,有三种模式。
- `STRICT`: 强制执行 mTLS,要求客户端和服务器使用 TLS 进行通信。这需要客户端和服务器具有有效的证书。
- `PERMISSIVE`: 允许客户端使用TLS或纯文本进行通信。这对于逐步迁移到 mTLS 的场景非常有用。
- `DISABLE`: 禁用 mTLS,不要求客户端和服务器使用 TLS 进行通信。
只能有一个网格范围的 Peer 认证策略,每个命名空间也只能有一个命名空间范围的 Peer 认证策略。当同一网格或命名空间配置多个网格范围或命名空间范围的 Peer 认证策略时,Istio 会忽略较新的策略。当多个特定于工作负载的 Peer 认证策略匹配时,Istio 将选择最旧的策略。
#### 实验
我们继续服用前面使用的 bookinfo 微服务,给 bookinfo 命名空间启用 mTLS。
```yaml
kubectl apply -f - < 如果只针对命名空间中的部分应用,可以使用:
>
> ```
> selector:
> matchLabels:
> app: my-app
> ```
>
在 RequestAuthentication 中,jwtRules 是一个配置项,用于定义如何验证和处理 JWT。
一个典型的 jwtRules 配置可能包括以下几个部分:
* `issuer`: 发行者,表示JWT的发行方,例如:`https://accounts.google.com`。这个字段用于验证JWT的iss(发行者)声明。
* `audiences`: 受众列表,表示接受JWT的一组实体。这个字段用于验证JWT的aud(受众)声明。例如:`["my-audience-1", "my-audience-2"]`。
* `jwksUri`: JSON Web Key Set(JWKS)的URL,用于获取JWT签名公钥。Istio会从这个URL下载公钥,用于验证JWT的签名。例如:`https://www.googleapis.com/oauth2/v3/certs`。
* `jwtHeaders`: 一个字符串数组,表示可以从HTTP请求头中获取JWT的头名称。默认情况下,Istio会从"Authorization"头中获取令牌。例如:`["x-jwt-assertion", "x-jwt-assertion-original"]`。
* `jwtParams`: 一个字符串数组,表示可以从HTTP请求参数中获取JWT的参数名称。例如:`["access_token"]`。
* `forward`: 一个布尔值,表示是否将JWT转发给上游服务。默认值为`false`,表示JWT令牌不会转发给上游服务。如果设置为`true`,则Istio会将令牌添加到请求头中,并转发给上游服务。
通过正确配置 jwtRules,Istio 可以对请求中的 JWT 进行验证,确保客户端访问服务网格中的服务时具有适当的授权。
#### AuthorizationPolicy 的定义
Istio 的 AuthorizationPolicy 是一种安全策略,用于控制在Istio服务网格中谁可以访问哪些服务。它提供了基于角色的访问控制(RBAC),允许定义细粒度的权限,以限制对特定服务、方法和路径的访问。AuthorizationPolicy 使用 Istio 的 Envoy 代理拦截并检查传入的请求,以确保它们满足定义的访问策略。
AuthorizationPolicy 的示例如下:
```yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-policy
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- to:
- operation:
paths: ["/delay/*"]
```
AuthorizationPolicy 的主要属性包括:
- `action`: 定义在规则匹配时要执行的操作。它可以是`ALLOW`(允许访问),`DENY`(拒绝访问)或`CUSTOM`(自定义操作,与自定义扩展插件一起使用)。
- `rules`: 定义一组访问策略规则。每个规则可以包括以下属性:
- `from`: 包含一个或多个源规范,用于定义允许访问的来源。可以包括`principals`(发起请求的主体,例如用户或服务帐户)和`namespaces`(发起请求的命名空间)。
- `to`: 包含一个或多个目标规范,用于定义允许访问的操作。可以包括`methods`(允许的HTTP方法,例如GET或POST)和`paths`(允许访问的路径,可以是精确路径或通配符路径)。
- `when`: 包含一组条件,用于定义规则生效的附加约束。例如,您可以使用`key`和`values`定义请求头匹配。
#### 实验
RequestAuthentication 的作用对象是 Kubernetes Service,主要有两种形式,一种是绑定 ingressgateway。
```yaml
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: ingress-jwt
namespace: bookinnfo
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
- issuer: "testing@secure.istio.io"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.17/security/tools/jwt/samples/jwks.json"
```

一种是绑定 Pod。
```yaml
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: frontend
namespace: default
spec:
selector:
matchLabels:
app: frontend
jwtRules:
- issuer: "testing@secure.istio.io"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.5/security/tools/jwt/samples/jwks.json"
```
考虑到一般不会在 istio-ingressgateway 这个入口网关上操作,所以下面我们使用第二种形式做实验。
#### 提供 jwksjson
首先是这个 YAML 文件中的 `jwksUri`,里面包含了一个 jwksjson 地址,里面包含了用于验证 token 是否有效的公钥。
在 C# 中,可以这样生成一个 jwksjson。
```csharp
using System;
using System.IO;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
namespace JWKSGenerator
{
class Program
{
static void Main(string[] args)
{
using var rsa = RSA.Create(2048);
var jwk = new RsaSecurityKey(rsa);
jwk.KeyId = Guid.NewGuid().ToString();
var jsonWebKey = JsonWebKeyConverter.ConvertFromRSASecurityKey(jwk);
var jwkJson = JsonConvert.SerializeObject(jsonWebKey);
var jwksJson = "{\"keys\": [" + jwkJson + "]}";
Console.WriteLine(jwksJson);
}
}
}
```

#### 创建 RequestAuthentication
考虑到官网示例中给出的 jwks.json 需要翻墙才能访问,我们可以直接将 jwks.json 放在 YAML 文件中。
```yaml
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: httpbin-jwt
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
jwtRules:
- issuer: "testing@secure.istio.io"
forwardOriginalToken: true
jwks: |
{
"keys": [
{
"e": "AQAB",
"kid": "DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ",
"kty": "RSA",
"n": "xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ"
}
]
}
```
或者继续使用官方的 jwksUri。
```yaml
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: httpbin-jwt
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
jwtRules:
- issuer: "testing@secure.istio.io"
forwardOriginalToken: true
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.5/security/tools/jwt/samples/jwks.json"
```

然后部署一个 AuthorizationPolicy,对 `/delay/*` 地址进行全放通,那么其它地址都需要进行验证才能放行。
```yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-policy
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- to:
- operation:
paths: ["/delay/*"]
```
> 执行命令之后,你可以使用以下命令查看是否正常:
>
> ```
> kubectl logs -n istio-system -l app=istiod
> ```
>
> 
然后查看策略规则对象:
```bash
kubectl get requestauthentication -n bookinfo
kubectl get authorizationpolicy -n bookinfo
```

最后通过 istio-ingressgateway 的节点端口来访问 `/status` 和 `/delay` ,会发现 `/status` 在没有 token 的情况下返回 403,而 `/delay` 可以正常访问。


如果我们需要验证,当 token 中的 issuer 为 example-issuer 才能访问时,可以使用:
```yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-policy
namespace: bookinfo
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- to:
- operation:
paths: ["/delay/*"]
when:
- key: request.auth.claims[iss]
values: ["example-issuer"]
```
> AuthorizationPolicy 的规则有很多,可以通过这些规则限制不同服务的访问策略。
>
> 请参考官方文档:https://istio.io/latest/zh/docs/concepts/security/#authorization-policies
所以 istio 这里一般做验证 jwt 是否有效,或者做路由地址的策略访问,但是如果有数十个上百个路由,使用 istio 配置就会好麻烦。但是依然不是我们想要的,因为在 istio 中配置不同应用访问权限和检验 token 比较繁琐,而且业务系统大多数情况下需要给用户单独配置各种 API 的访问权限。
================================
# 模块:Kubernetes
Kubernetes 是一个开源的容器编排平台,提供了自动化部署、扩展和管理容器化应用的功能,帮助开发者更高效地运行和管理应用。
--------------------------------
标题:文档说明
来源:/zh/kubernetes/README
# 文档说明
作者:痴者工良
作者博客地址:
[https://www.whuanle.cn](https://www.whuanle.cn)
[https://www.cnblogs.com/whuanle](https://www.cnblogs.com/whuanle)
教程地址:
[https://docs.whuanle.cn/zh/kubernetes](https://docs.whuanle.cn/zh/kubernetes)
简介:
这是一本关于 Kubernetes 基础的书。
本书分为 五大章,每个大章一个目录,目录中都有一个导读,导读会大概介绍本章会讲解什么内容,需要掌握什么知识,每章中有多个小章,每个小章是一个单独的知识点。
本书覆盖了 Kubernetes 基础的大多数知识点,读者既可以了解具体的知识内容,也可以跟着做实验,因为每个部分都是经过笔者多次实践演练,得到结果验证,几乎每一章都有大量的实验。
第一章介绍了 Docker、Kubernetes 的一些基础知识;
第二章介绍了如何部署 Kubernetes 集群;
第三章介绍了 Pod 的大部分知识和实践实验,如何通过各种方式部署调度 Pod;
第四章介绍了 Kubernetes 的网络知识,解决如何解决应用的网络问题;
第五章介绍了存储卷的使用方法和分布式存储。
> 笔者注:云原生应用,不仅仅是用 Kubernetes 部署,还需要解决日志收集、集群/应用监控等一系列问题,使用合理的技术方案做好基础设施的架构。
* [1.基础知识](1.basic/README.md)
* [导读](1.basic/README.md)
* [1.1 说透 Docker:基础](1.basic/1.docker.md)
* [1.2 说透 Docker: 虚拟化](1.basic/2.virtual.md)
* [1.3 了解 Docker 网络](1.basic/3.docker_network.md)
* [1.4 Docker 和 Pod](1.basic/4.pod_docker.md)
* [1.5 K8S入门基础](1.basic/5.k8s.md)
* [2.部署和配置](2.deploy/README.md)
* [导读](2.deploy/2.deploy.md)
* [2.1 使用 Minikube 部署](2.deploy/1.minikube.md)
* [2.2 使用 kubeadm 部署](2.deploy/2.kubeadm.md)
* [2.3 CKAD认证中的部署教程](2.deploy/3.kubeadm_ckad.md)
* [2.4 国内代理](2.deploy/4.kubeadm_proxy.md)
* [2.5 Dashboard](2.deploy/5.dashboard.md)
* [3.Pod部署和调度](3.pod/README.md)
* [导读](3.pod/README.md)
* [3.1 Pod](3.pod/1.pod.md)
* [3.2 Deployment部署](3.pod/2.deployment.md)
* [3.3 副本集(ReplicaSet)](3.pod/3.replica.md)
* [3.34 Pod 端口映射](3.pod/4.pod_network.md)
* [3.5 Pod 升级、回滚](3.pod/5.update.md)
* [3.6 Pod 缩放](3.pod/6.scale.md)
* [3.7.Pod 标签](3.pod/7.lable.md)
* [3.8 Pod 调度](3.pod/8.schedule.md)
* [3.9 Jobs、CronJobs](3.pod/9.jobs_cronjobs.md)
* [4.Kubernetes 网络](4.network/README.md)
* [导读](4.network/README.md)
* [4.1 Kubernetes 网络](4.network/1.network.md)
* [4.2 Endpoint](4.network/2.endpoint.md)
* [4.3 ingress](4.network/3.ingress.md)
* [4.4 服务发现](4.network/4.discovery.md)
* [5.volumes](5.volumes/README.md)
* [导读](5.volumes/README.md)
* [5.1 卷](5.volumes/1.volumes.md)
* [5.2 secret 和 ConfigMap 卷](5.volumes/2.secret_configmap.md)
* [5.3 NFS卷](5.volumes/3.nfts.md)
* [5.4 持久化卷](5.volumes/4.pv_pvc.md)
* [Kubernetes 命令概览](k8s.md)
* [CKAD 认证帮助](ckad.md)
--------------------------------
标题:导读
来源:/zh/kubernetes/1.basic/README
# 第一章:基础知识
## 导读
本章将会介绍 Docker 和 Kubernetes 的一些基础知识,掌握 Docker 的组成结构以及 Docker 是怎么隔离容器、隔离硬件资源的,了解为什么用 Kubernetes,Kubernetes 的组成、结构等,我们会学习到很多 K8S 的术语。
由于本章的内容可能看起来比较枯燥,建议第一遍阅读时,只快速了解,读完后面的章节后、练习命令、上手实践,对各方面的技术了解后,再重新看第一大章。
关于 虚拟化、容器化等术语,每个人的理解都不一样,笔者只是讲述自己的理解,如有争议,以主流说法为准。
--------------------------------
标题:1.1 Docker:基础
来源:/zh/kubernetes/1.basic/1.docker
# 1.1 Docker:基础
既然要学习 Kubernetes,相信各位读者都已经使用过 Docker 了,Docker 的入门是比较容易的,但 Docker 的网络和存储、虚拟化等技术是相当复杂的,Docker 的技术点比较多,在本章中将会介绍 Docker 的一些知识,期待能够帮助读者加深对 Docker 的理解,但是本书不会深入 Docker 的技术细节,读者如有兴趣,请自行参考官方资料。
## 容器化应用
### 什么是容器化应用
containerized applications 指容器化的应用,我们常常说使用镜像打包应用程序,使用 Docker 发布、部署应用程序,那么当你的应用成功在 Docker 上运行时,则称这个应用是 containerized applications。
### 应用怎么打包
容器化应用的最主要特征是使用镜像打包**应用的运行环境以及应用程序**,可以通过 Docker 启动这个镜像,进而将 应用程序启动起来。
将一个应用程序打包为镜像,大约分为以下过程:
* 编写 Dockerfile 文件 -- 定义构建镜像的流程
* 选择一个基础镜像(操作系统) -- 操作系统
* 安装应用的需要的环境 -- 运行环境
* 复制程序文件 -- 应用程序
* 启动 Dockerfile -- 生成镜像
```mermaid
sequenceDiagram
操作系统->>运行环境: Ubuntu 18.04
Note right of 运行环境: .NET Runtime/Java Runtime
运行环境-->>Web程序: 安装运行环境
```
### Docker 镜像组成
以 .NET Core(C#) 程序为例,一个 Docker 镜像的层次如下图所示:

在 Docker 镜像中,操作系统是高度精简的,可能只有一个精简的 Shell,甚至没有 Shell。而且镜像中的操作系统还不包含内核,**容器都是共享所在的宿主机的内核**。所以有时会说容器仅包含必要的操作系统(通常只有操作系统文件和文件系统对象),容器中查看到的 Linux 内核版本与宿主机一致。
Docker 镜像的是由一系统文件组成的。

依赖环境包含了程序启动时需要的依赖库,如 Java SDK、libc.so 等。
### 联合文件系统
Linux 有名为 Unionfs 的文件系统服务,可以将不同文件夹中的文件联合到一个文件夹中。Unionfs 有称为分支的概念,一个分支包含了多个目录和文件,多个分支可以挂载在一起,在挂载时,可以指定一个分支优先级大于另一个分支,这样当两个分支都包含相同的文件名时,一个分支会优先于另一个分支,在合并的目录中,会看到高优先级分支的文件。

Docker 中,层层组成镜像的技术也是联合文件系统,Union File System。Docker 镜像中的操作系统是根文件系统,在上一小节的图片中,可以看到有 bin、boot 等目录。我们都知道,Docker 镜像是由多层文件组成的,在上面的示例图片中有三层组成:根文件系统、环境依赖包、应用程序文件。当镜像层生成后,便不能被修改,如果再进行操作,则会在原来的基础上生成新的镜像层,层层联合,最终生成镜像。当然生成的镜像可能会因为层数太多或者操作过多,导致出现大量冗余,镜像臃肿。
Docker 的镜像分层是受 Linux Unionfs 启发而开发的,Docker 支持多种文件联合系统,如 AUFS、OverlayFS、VFS 等。
Docker 在不同系统中可以选择的联合文件系统:
| Linux发行版 | 推荐的存储驱动程序 | 替代驱动程序 |
| :---------- | :----------------- | :-------------------------------------------- |
| Ubuntu | `overlay2` | `overlay` `devicemapper`, `aufs`, `zfs`,`vfs` |
| Debian | `overlay2` | `overlay`, `devicemapper`, `aufs`,`vfs` |
| CentOS | `overlay2` | `overlay`, `devicemapper`, `zfs`,`vfs` |
> **[info] 提示**
>
> Docker Desktop for Mac 和 Docker Desktop for Windows 不支持修改存储驱动程序,只能使用默认存储驱动程序。
### Linux 内核
既然 Docker 容器需要与 Linux 内核结合才能使用,那么我们看一下 Linux 内核的功能,稍微了解一下 Linux 内核在支撑 Docker 容器运作中起到什么作用。
Linux 内核主要包含以下功能:
* 内存管理:追踪记录有多少内存存储了什么以及存储在哪里;
* 进程管理:确定哪些进程可以使用中央处理器(CPU)、何时使用以及持续多长时间;
* 设备驱动程序:充当硬件与进程之间的调解程序/解释程序;
* 系统调用和安全防护:接受程序请求调用系统服务;
* 文件系统:操作系统中负责管理持久数据的子系统,在 Linux 中,一切皆文件。
Linux 层次结构如下:

Docker 容器中包含了一个操作系统,包含简单的 shell 或者不包含,其层次结构如图所示:

## Docker 结构
本节将了解 Docker 的组成部件和结构。
### Docker 服务与客户端
Docker 由 Service 和 Client 两部分组成,在服务器上可以不安装 Docker Client,可以通过 Http Api 等方式与 Docker Servie 通讯。
在安装了 Docker 的主机上执行命令 `docker version` 查看版本号。
```shell
Client: Docker Engine - Community
Version: 20.10.7
API version: 1.41
Go version: go1.13.15
Git commit: f0df350
Built: Wed Jun 2 11:58:10 2021
OS/Arch: linux/amd64
Context: default
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 20.10.7
API version: 1.41 (minimum version 1.12)
Go version: go1.13.15
Git commit: b0f5bc3
Built: Wed Jun 2 11:56:35 2021
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.4.6
GitCommit: d71fcd7d8303cbf684402823e425e9dd2e99285d
runc:
Version: 1.0.0-rc95
GitCommit: b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
docker-init:
Version: 0.19.0
GitCommit: de40ad0
```
### Docker 客户端
要想跟 Docker Server 通讯,可以使用 Restful API、UNIX 套接字或网络接口(Socket)。Docker 官方的客户端是一个二进制命令行程序,使用 Go 语言编写,我们也可以使用 C#、Java 等语言写一个类似的程序,Docker 客户端不需要安装到 Docker Server 所在的主机,Client 跟 Server 可以远程通讯。
Docker 的客户端是许多 Docker 用户与 Docker 交互的主要方式,当我们使用 `docker run` 之类的命令时,客户端会将这些命令发送到 Docker Server,由 Docker Server 解析并执行命令。
Docker for Linux 中最为常见的同主机通讯方式是 Unix 域套接字。很多软件都支持使用域套接字与 Docker 通讯,例如 CI/CD 软件 Jenkins,使用域套接字连接 Docker,能够利用 Docker 启动容器构建应用程序以及使用 Docker 来做一些不可描述的事情。

### 容器运行时
容器运行时是提供运行环境并启动容器的软件,我们最常听说的是 Docker,此外还有 [containerd](https://containerd.io/docs/)、[CRI-O](https://cri-o.io/#what-is-cri-o) 等。可以毫不夸张的说,整个 Kubernetes 建立在容器之上。
默认情况下,Kubernetes 使用 容器运行时接口(Container Runtime Interface,CRI) 来与服务器中容器运行时交互。所以 Kubernetes 支持多种容器软件,但只能使用一种容器运行时进行工作,在有多个容器运行时的情况下,我们需要指定使用何种运行时,如果你不指定运行时,则 kubeadm 会自动尝试检测到系统上已经安装的运行时, 方法是扫描一组众所周知的 Unix 域套接字。
Linux 是多进程操作系统,为了让多个系统中的多个进程能够进行高效的通讯,出现和很多方法,其中一种是域套接字(Unix domain socket),只能用于在同一计算机中的进程间通讯,但是其效率高于网络套接字(socket),域套接字不需要经过网络协议处理,通过系统调用将数据从一个进程复制到另一个进程中。
域套接字使用一个 .sock 文件进行通讯,常见的容器软件其对应域套接字如下:
| 运行时 | 域套接字 |
| ---------- | ------------------------------- |
| Docker | /var/run/dockershim.sock |
| containerd | /run/containerd/containerd.sock |
| CRI-O | /var/run/crio/crio.sock |
> **[info]**
>
> 同一主机下常见进程通讯方式有 共享内存、消息队列、管道通讯(共享文件)。
>
> Unux 域套接字是套接字和管道之间的混合物。
> 在 Linux 中,有很多进程,为了让多个进程能够进行通讯,出现和很多方法,其中一种是套接字(socket)。一般的 socket 都是基于 TCP/IP 的,称为网络套接字,可以实现跨主机进程通讯。在 Linux 中有一种套接字,名为**域套接字**,只能用于在同一计算机中的进程间通讯,但是其效率高于网络套接字。域套接字使用一个 .sock 文件进行通讯。
当计算机中有多种容器运行时,Kubernetes 默认优先使用 Docker。
如果你想了解 CRI ,请点击:
[https://github.com/kubernetes/community/blob/master/contributors/devel/sig-node/container-runtime-interface.md](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-node/container-runtime-interface.md)
### Docker 引擎
Docker 引擎也可以说是 Docker Server,它由 Docker 守护进程(Docker daemon)、containerd 以及 runc 组成。
当使用 Docker client 输入命令时,命令会被发送到 Docker daemon ,daemon 会侦听请求并管理 Docker 对象,daemon 可以管理 镜像、容器、网络和存储卷等。
下面这个图是新 Docker 版本的结构组成。

### Docker 引擎变化
Docker 首次发布时,Docker 引擎由两个核心组件构成:LXC 和 Docker daemon,这也是很多文章中称 Docker 是基于 LXC 的原因,旧版本的 Docker 利用了 LXC、cgroups、Linux 内核编写。接下来我们了解一下 LXC 。
LXC (Linux Container)是 Linux 提供的一种内核虚拟化技术,可以提供轻量级的虚拟化,以便隔离进程和资源,它是操作系统层面上的虚拟化技术。LXC 提供了对诸如命名空间(namespace) 和控制组(cgroups) 等基础工具的操作能力,它们是基于 Linux 内核的容器虚拟化技术。我们不需要深入了解这个东西。

Docker 一开始是使用 LXC 做的,LXC 是一个很牛逼的开源项目,但是随着 Docker 的成熟,Docker 开始抛弃 LXC,自己动手手撕容器引擎。
为什么 Docker 要抛弃 LXC 呢?首先,LXC 是基于 Linux 的。这对于一个立志于跨平台的 Docker 来说是个问题,离开 LXC,怎么在 MAC、Windows 下运行?其次,如此核心的组件依赖于外部工具,这会给项目带来巨大风险,甚至影响其发展。
> **[info]**
>
> 哈哈哈。。。其实笔者觉得不支持 Windows 也罢。。。
### Docker 引擎的架构
下面是一张 Docker 的架构图。

Docker client 和 Docker daemon 在前面已经介绍过了,接下来介绍其他组件。
#### containerd
containerd 是一个开源容器引擎,是从 Docker 开源出去的。之前有新闻说 Kubernetes 不再支持 Docker,只支持 containerd,很多人以为 Docker 不行了。
一开始 Docker 是一个 “大单体”,随着 Docker 的成长,Docker 开始进行模块化,Docker 中的许多模块都是可替换的,如 Docker 网络。支持容器运行的核心代码自然也抽出来,单独做一个模块,便是 containerd。Kubernetes 不再支持 Docker,只不过是降低依赖程度,减少对其他模块的依赖,只集中在 containerd 上。当我们安装 Docker 时,自然会包含 containerd。如果我们不需要 Docker 太多组件,那么我们可以仅仅安装 containerd,由 Kubernetes 调度,只不过我们不能使用 Docker client 了。因此可以说,Kubernetes 不再支持 Docker,并不代表会排斥 Docker。
containerd 的主要任务是容器的生命周期管理,如启动容器、暂停容器、停止容器等。containerd 位于 daemon 和 runc 所在的 OCI 层之间。
#### shim
shim 它的作用非常单一,那就是实现 CRI 规定的每个接口,然后把具体的 CRI 请求“翻译”成对后端容器项目的请求或者操作。
这里要区别一下,dockershim 和 containerd-shim,dockershim 是一个临时性的方案,dockershim 会在 Kubernetes v1.24中 删除(2022年),这也是 Kubernetes 不再支持 Docker 的另一组件。
> **[info] 提示**
>
> CRI 即 Container Runtime Interface,容器运行时接口,容器引擎要支持 Kubernetes ,需要实现 CRI 接口,例如 runc 、crun 两种是常见的 Container Runtime。
shim 是容器进程的父进程,shim 的生命周期跟容器一样长,shim 是一个轻量级的守护进程,它与容器进程紧密相关,但是 shim 与容器中的进程完全分离。shim 可以将容器的 stdin、stdout、srderr 流重定向到日志中,我们使用 `docker logs` 即可看到容器输出到控制台的流。
关于 shim,我们就先了解到这里,后面会继续讲解一个示例。
#### runc
runc 实质上是一个轻量级的、针对 Libcontainer 进行了包装的命令行交互工具,runc 生来只有一个作用——创建容器,即 runc 是一个由于运行容器的命令行工具。
> **[info] 提示**
>
> Libcontainer 取代了早期 Docker 架构中的 LXC。
如果主机安装了 Docker,我们可以使用 `runc --help` 来查看使用说明。我们可以这样来理解 runc,runc 是在隔离环境生成新的进程的工具,在这个隔离环境中有一个专用的根文件系统(ubuntu、centos等)和新的进程树,这个进程树的根进程 `PID=1`。
> **[success] 博客推荐**
>
> 笔者在查阅资料时,发现了这个大佬的博客,在这个大佬的博客中学会了很多东西。
>
> 博客推荐:[https://iximiuz.com/en/](https://iximiuz.com/en/)
在后面的节中,我们将继续了解 Docker 中的网络和存储,并开始探究与 Kubernetes 相关的知识点。
--------------------------------
标题:1.2 虚拟化
来源:/zh/kubernetes/1.basic/2.virtual
# 1.2 虚拟化
本章内容将讲解 虚拟化、虚拟化本质、namespace、cgroups。
## Docker 虚拟化
### 关于Docker
本小节将介绍 Docker 虚拟化的一些特点。
Docker 是一个开放源代码软件项目,自动化进行应用程序容器化部署,借此在 Linux 操作系统上,提供一个额外的软件抽象层,以及操作系统层虚拟化的自动管理机制。 -From wiki

在接触 Docker 的过程中,或多或少会了解到 Docker 的虚拟化,最常见的介绍方式是对比 Docker 和虚拟机之间的差别,笔者这里也给出两者的对比表格,以便后面详细地展开来讲。
| | **虚拟机** | **Docker 容器** |
| -------- | -------------------------------- | -------------------------------------------- |
| 隔离程度 | 硬件级进程隔离 | 操作系统级进程隔离 |
| 系统 | 每个虚拟机都有一个单独的操作系统 | 每个容器可以共享操作系统(共享操作系统内核) |
| 启动时间 | 需要几分钟 | 几秒 |
| 体积大小 | 虚拟机镜像GB级别 | 容器是轻量级的(KB/MB) |
| 启动镜像 | 虚拟机镜像比较难找到 | 预建的 docker 容器很容易获得 |
| 迁移 | 虚拟机可以轻松迁移到新主机 | 容器被销毁并重新创建而不是移动 |
| 创建速度 | 创建 VM 需要相对较长的时间 | 可以在几秒钟内创建容器 |
| 资源使用 | GB级别 | MB级别 |
Docker 中的虚拟化是依赖于 Windows 和 Linux 内核的,在 Windows 上会要求开启 Hyper-V,在 Linux 上需要依赖 namespace 和 cgroups 等,因此这里就不过多介绍 Docker 了,后面主要介绍 Linux 上的虚拟化技术。
### 传统虚拟化部署方式
传统虚拟化方式是在硬件抽象级别虚拟化,其特点是 虚拟化程度高。

传统虚拟化方式的优点是:
1,虚拟机之间通过虚拟化技术隔离互不影响
2,物理机上可部署多台虚拟机,提升资源利用率
3,应用资源分配、扩容通过虚拟管理器直接可配置
4,支持快照、虚拟机克隆多种技术,快速部署、容灾减灾
传统虚拟化部署方式的缺点:
1, 资源占用高,需要额外的操作系统镜像,需要占用GB级别的内存以及数十GB存储空间。
2,启动速度慢,虚拟机启动需要先启动虚拟机内操作系统,然后才能启动应用。
3,性能影响大,应用 => 虚拟机操作系统=> 物理机操作系统=> 硬件资源
## Linux 虚拟化
本节简单地讲解 Docker 的实现原理,读者可以从中了解 Linux 是如何隔离资源的、Docker 又是如何隔离的。
我们知道,操作系统是以一个进程为单位进行资源调度的,现代操作系统为进程设置了资源边界,每个进程使用自己的内存区域等,进程之间不会出现内存混用。Linux 内核中,有 cgroups 和 namespaces 可以为进程定义边界,使得进程彼此隔离。
### namespace 环境隔离
在容器中,当我们使用 top 命令或 ps 命令查看机器的进程时,可以看到进程的 Pid,每个进程都有一个 Pid,而机器的所有容器都具有一个 Pid = 1 的基础,但是为什么不会发生冲突?容器中的进程可以任意使用所有端口,而不同容器可以使用相同的端口,为什么不会发生冲突?这些都是资源可以设定边界的表现。
在 Linux 中,namespace 是 Linux 内核提供的一种资源隔离技术,可以将系统中的网络、进程环境等进行隔离,使得每个 namespace 中的系统资源不再是全局性的。目前有以下 6 种资源隔离,Docker 也基本在这 6 种资源上对容器环境进行隔离。
读者可以稍微记忆一下这个表格,后面会使用到。
| namespace | 系统调用参数 | 隔离内容 |
| --------- | ------------- | -------------------------- |
| UTS | CLONE_NEWUTS | 主机名和域名 |
| IPC | CLONE_NEWIPC | 信号量、消息队列、共享内存 |
| PID | CLONE_NEWPID | 进程编号 |
| Network | CLONE_NEWNET | 网络设备、网络栈、端口 |
| Mount | CLONE_NEWNS | 文件系统挂载 |
| User | CLONE_NEWUSER | 用户和用户组 |
> **[info] 关于 Mount**
>
> namespace 的 Mount 可以实现将子目录挂载为根目录。
#### unshare
Linux 中,unshare 命令行程序可以创建一个 namespace,并且根据参数创建在 namespace 中隔离各种资源,在这里我们可以用使用这个工具简单地创建一个 namespace。
为了深刻理解 Linux 中的 namespace,我们可以在 Linux 中执行:
```shell
unshare --pid --fork --mount-proc /bin/bash
```
> `--pid` 仅隔离进程。
这命令类似于 `docker run -it {image}:{tag} /bin/bash` 。当我们执行命令后,终端会进入一个 namespace 中,执行 top 命令查看进程列表。
```
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 160188 8276 5488 S 0.0 0.4 9:35.58 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.08 kthreadd
3 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_gp
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_par_gp
```
可以看到,进程 PID 是从 1 开始的,说明在这个 namespace 中,与主机的进程是隔离开来的。
这个命令中,只隔离了进程,因为并没有隔离网络,因此当我们执行 `netstat --tlap` 命令时,这个命名空间的网络跟其它命名空间的网络是相通的。
在执行 unshare 命令前,使用 pstree 命令查看进程树:
```shell
init─┬─2*[init───init───bash]
├─init───init───bash───pstree
├─init───init───fsnotifier-wsl
├─init───init───server───14*[{server}]
└─2*[{init}]
```
为了方便比较,我们使用 `unshare --pid top` 创建一个 namespace,对比执行了 unshare 命令后:
```shell
$> pstree -lha
init
├─init
│ └─init
│ └─bash
│ └─sudo unshare --pid top
│ └─top
├─init
│ └─init
│ └─bash
│ └─pstree -lha
├─init
│ └─init
│ └─fsnotifier-wsl
├─init
│ └─init
│ └─bash
├─init
│ └─init
│ └─server --port 29687 --instance WSL-Ubuntu
│ └─14*[{server}]
└─2*[{init}]
```
而在 namespace 中,查看 top 显示的内容,发现:
```shell
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 1904 1136 1020 S 0.0 0.0 0:08.38 init
```
通过进程树可以看到,不同 namespace 内的进程处于不同的树支,他们的进程 PID 也是相互独立的。其功能类似于 Docker 中的 runc。
由于笔者对 Linux 了解不深,这部分内容就不深入探究了。
在 `unshare` 命令中,`--pid` 参数创建 隔离进程的命名空间,此外,还可以隔离多种系统资源:
* mount :命名空间具有独立的挂载文件系统;
* ipc:Inter-Process Communication (进程间通讯)命名空间,具有独立的信号量、共享内存等;
* uts:命名空间具有独立的 hostname 、domainname;
* net:独立的网络,例如每个 docker 容器都有一个虚拟网卡;
* pid:独立的进程空间,空间中的进程 Pid 都从 1 开始;
* user:命名空间中有独立的用户体系,例如 Docker 中的 root 跟主机的用户不一样;
* cgroup:独立的用户分组;
#### Go 简单实现 进程隔离
在前面我们使用了 unshare 创建命名空间,在这里我们可以尝试使用 Go 调用 Linux 内核的 namespace,通过编程代码创建隔离的资源空间。
Go 代码示例如下:
```go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("/bin/bash")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS |
syscall.CLONE_NEWIPC |
syscall.CLONE_NEWNS |
syscall.CLONE_NEWNET |
syscall.CLONE_NEWPID |
syscall.CLONE_NEWUSER,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatalln(err)
}
}
```
> **[info] 提示**
>
> 前面已经提到过 UTS 等资源隔离,读者可以参考表格中的说明,对照代码理解 Cloneflags 的作用。
>
> 代码示例参考 陈显鹭《自己动手写 Docker》一书。
在这个代码中,我们启动了 Linux 中的 sh 命令,开启一个新的进程,这个进程将会使用新的 IPC、PID 等隔离。
读者可以在 Linux 中,执行 `go run main.go` ,即可进入新的命名空间。

限于个人水平和篇幅有限,关于 namespace 的介绍就到这里。
### cgroups 硬件资源隔离
前面提到的 namepace 是逻辑形式使得进程之间相互不可见,形成环境隔离,这跟 Docker 容器的日常使用是一样的,隔离根目录,隔离网络,隔离进程 PID 等。
当然,Docker 处理环境隔离外,还能限制每个容器使用的物理资源,如 CPU 、内存等,这种硬件资源的限制是基于 Linux 内核的 cgroups 的。
在 Docker 中限制容器能够使用的资源量参数示例:
```shell
-m 4G --memory-swap 0 --cpu-period=1000000 --cpu-quota=8000000
```
cgroups 是 control groups 的缩写,是 Linux 内核提供的一种可以进程所使用的物理资源的机制。
cgroups 可以控制多种资源,在 cgroups 中每种资源限制功能对应一个子系统,可以使用命令查看:
```shell
mount | grep cgroup
```

>**[info] 提示**
>
>每种子系统的功能概要如下:
>
>- `blkio` — 该子系统对进出块设备的输入/输出访问设置限制,如 USB 等。
>- `cpu` — 该子系统使用调度程序来提供对 CPU 的 cgroup 任务访问。
>- `cpuacct` — 该子系统生成有关 cgroup 中任务使用的 CPU 资源的自动报告。
>- `cpuset` — 该子系统将单个 CPU和内存节点分配给 cgroup 中的任务。
>- `devices` — 该子系统允许或拒绝 cgroup 中的任务访问设备。
>- `freezer` — 该子系统在 cgroup 中挂起或恢复任务。
>- `memory` — 该子系统对 cgroup 中的任务使用的内存设置限制,并生成有关自动报告。
>- `net_cls`— 允许 Linux 流量控制器 ( `tc`) 识别源自特定 cgroup 任务的数据包。
>- `net_prio` — 该子系统提供了一种动态设置每个网络接口的网络流量优先级的方法。
>- `ns`—*命名空间*子系统。
>- `perf_event` — 该子系统识别任务的 cgroup 成员资格,可用于性能分析。
>
>详细内容请参考:[redhat 文档](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/ch01)
我们也可以使用 ` lssubsys` 命令,查看内核支持的子系统。
```
$> lssubsys -a
cpuset
cpu
cpuacct
blkio
memory
devices
freezer
net_cls
perf_event
net_prio
hugetlb
pids
rdma
```
> **[info] 提示**
>
> Ubuntu 可以使用 `apt install cgroup-tools` 安装工具。
为了避免篇幅过大,读者只需要知道 Docker 限制容器资源使用量、CPU 核数等操作,其原理是 Linux 内核中的 cgroups 即可,笔者这里不再赘述。
## 聊聊虚拟化
本节内容将从底层角度,聊聊虚拟化。
### 理论基础
#### 计算机层次结构
从语言角度,一台由软硬件组成的通用计算机系统可以看作是按功能划分的多层机器级组成的层次结构。
如果从语言角度来看,计算机系统的层次结构可用下图所示。

【图来源:《计算机组成原理》天勤考研 1.2.5 计算机系统的层次结构】
我们平时使用的笔记本、安卓手机、平板电脑、Linux 服务器等,虽然不同机器的系统和部分硬件差异很大,但是其系统结构是一致的。从 CPU 中晶体管、寄存器 到 CPU 指令集,再到操作系统、汇编,现在使用的通用计算机基本上这种结构。
下面讲解一下不同层次的主要特点。
计算机的最底层是硬联逻辑级,由门电路,触发器等逻辑电路组成,特征是使用极小的元件构成,表示了计算机中的 0、1。

微程序是使用微指令编写的,一个微程序即一个机器指令,一般直接由硬件执行,它可以表示一个最简单的操作。例如一个加法指令,由多个逻辑元件构成一个加法器,其元件组成如下图所示(图中为一个 8 位全加器)。

传统机器语言机器级是处理器的指令集所在,我们熟知的 X86、ARM、MIPS、RISC-V 等指令集,便是在这个层次。程序员使用指令集中的指令编写的程序,由低一层微程序解释。
操作系统机器层是从操作系统基本功能来看的,操作系统需要负责管理计算机中的软硬件资源,如内存、设备、文件等,它是软硬件的交互界面。常用的操作系统有 Windows、Linux、Unix 等。这个层次使用的语言是机器语言,即 0、1 组成的二进制代码,能够由计算机直接识别和执行。
汇编语言机器层顾名思义是汇编语言所在的位置,汇编语言与处理器有关,相同类型的处理器使用的汇编语言集是一致的。汇编语言需要被汇编语言程序变换为等效的二进制代码目标程序。由于计算机中的资源被操作系统所管理,因此汇编语言需要在操作系统的控制下进行。
到了高级语言机器层,便是我们使用的 C、C++ 等编程语言,高级语言是与人类思维相接近的语言。
### 软硬件实现等效
计算机的某些功能即可以由硬件实现,也可以由软件来实现。即软件和硬件在功能意义上是等效的。
一个功能使用硬件来实现还是使用软件来实现?
硬件实现:速度快、成本高;灵活性差、占用内存少。
软件实现:速度低、复制费用低;灵活性好、占用内存多。
虚拟化技术是将原本 硬件实现的功能,使用软件来实现,它们在性能、价格、实现的难易程度是不同的。一个功能既可以使用硬件实现,也可以使用软件实现,也可以两者结合实现,可能要根据各种人力成本、研发难度、研发周期等考虑。
## 虚拟化
虚拟化(技术)或虚拟技术是一种资源管理技术,将计算机的各种实体资源(CPU、内存、磁盘空间、网络适配器等),予以抽象、转换后呈现出来并可供分割、组合为一个或多个计算机配置环境。
### 不同层次的虚拟化
我们应该在很多书籍、文章中,了解到虚拟机跟 Docker 的比较,了解到 Docker 的优点,通过 Docker 打包镜像后可以随时在别的地方运行而不需要担心机器的兼容问题。但是 Docker 的虚拟化并不能让 Linux 跑 Windows 容器,也不能让 Windows 跑 Linux 容器,更不可能让 x86 机器跑 arm 指令集的二进制程序。但是 VMware 可以在 Windows 运行 Linux 、Mac 的镜像,但 WMWare 也不能由 MIPS 指令构建的 Linux 系统。
Docker 和 VMware 都可以实现不同程度的虚拟化,但也不是随心所欲的,它们虚拟化的程度相差很大,因为它们是在不同层次进行虚拟化的。

> **[Info] 提示**
>
> 许多虚拟化软件不单单是在一个层面上,可能具有多种层次的虚拟化能力。
在指令集级别虚拟化中,从指令系统上看,就是要在一种机器上实现另一种机器的指令系统。例如,QEMU 可以实现在 X64 机器上模拟 ARM32/64、龙芯、MIPS 等处理器。
虚拟化程度在于使用硬件实现与软件实现的比例,硬件部分比例越多一般来说性能就会越强,软件部分比例越多灵活性会更强,但是性能会下降,不同层次的实现也会影响性能、兼容性等。随着现在计算机性能越来越猛,很大程度上产生了性能过剩;加之硬件研发的难度越来越高,越来越难突破,非硬件程度的虚拟化将会越来越广泛。
--------------------------------
标题:1.3 了解 Docker 网络
来源:/zh/kubernetes/1.basic/3.docker_network
# 1.3 了解 Docker 网络
本章将会简单地讲述 Docker 中的网络,了解 Docker 网络的模式。
## Docker 的四种网络模式
Docker 有 bridge、none、host、container 四种网络模式,提供网络隔离、端口映射、容器间互通网络等各种支持,下面开门见山地直接介绍这四种网络模式。
这四种网络模式可以通过启动容器的时候指定,其命令或参数个数如下:
| 网络模式 | 参数 | 说明 |
| ------------- | ------------ | ------------------------------------------------------------ |
| host模式 | -–net=host | 容器和宿主机共享 Network namespace。 |
| container模式 | –-net={id} | 容器和另外一个容器共享 Network namespace。 kubernetes 中的pod就是多个容器共享一个 Network namespace。 |
| none模式 | –-net=none | 容器有独立的Network namespace,但并没有对其进行任何网络设置,如分配 veth pair 和网桥连接,配置IP等。 |
| bridge模式 | -–net=bridge | 默认为该模式 ,通过 -p 指定端口映射。 |
这四种模式可以理解成 Docker 怎么虚拟化容器的网络,隔离程度和共享程度。
### bridge 模式
使用 Docker 创建一个 bridge 模式的容器命令格式如下:
```shell
docker run -itd -p 8080:80 nginx:latest
```
bridge 模式称为网桥模式,首先 Docker 会在主机上创建一个名为 docker0 的虚拟网桥,这个虚拟网络处于七层网络模型的数据链路层,每当创建一个新的容器时,容器都会通过 docker0 与主机的网络连接,docker0 相当于网桥。
使用 bridge 模式新创建的容器,其内部都有一个虚拟网卡,名为 eth0,容器之间可以通过 172.17.x.x 相互访问。
一般情况下,网桥默认 IP 范围是 172.17.x.x ,可以在宿主机执行 ifpconfig 命令查看所有网卡,里面会包含 Docker 容器的虚拟网卡,可以查看某个容器的 ip。在容器中,也可以使用 ifconfig 命令查看自身的容器 ip:
```shell
root@cda6958393cb:/var# ifconfig
eth0: flags=4163 mtu 1500
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet)
RX packets 347 bytes 9507996 (9.5 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 278 bytes 22384 (22.3 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73 mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
```
可以看到,此容器的 ip 是 172.17.0.2。
使用了 bride 创建的容器,其网络与主机以及其他容器隔离,以太网接口、端口、路由表以及 DNS配置 都是独立的。每个容器都好像是一个独立的主机 ,这便是 bridge(网桥)的作用。但是因为 docker0 的存在,对于容器来说,可以通过 ip 访问别的容器。

容器1 可以通过 172.17.0.3 访问容器2,同样,主机也可以使用这个 ip 访问容器2 中的服务。
> **[Error] 提示**
>
> bridge 模式 是默认模式,即使是 使用 `docker run -itd nginx:latest` 命令启动容器,也会创建一个虚拟 IP。
### none 模式
这种网络模式下容器只有 lo 回环网络,没有其他网卡,这种类型的网络没有办法联网,外界也无法访问它,封闭的网络能很好地保证容器的安全性。
创建 none 网络的容器:
```shell
docker run -itd --net=none nginx:latest
```
```
root@5a67da130f62:/var# ./ifconfig
lo: flags=73 mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
```

### host 模式
host 模式会让容器与主机共享网络,此时映射的端口可能会生产冲突,但是容器的其余部分(文件系统、进程等)依然是隔离的,此时容器与宿主机共享网络。

### container 模式
container 模式可以让多个容器之间相互通讯,即容器之间共享网络。
首先启动一个 A 容器,A 一般为 bridge 网络,接着 B 使用 `–-net={id}` 连接到 A 中,使用 A 的虚拟网卡,此时 A、B 共享网络,可以接着加入 B、C、D 等容器。

--------------------------------
标题:1.4 容器与 Pod
来源:/zh/kubernetes/1.basic/4.pod_docker
# 1.4 容器与 Pod
现在 Docker 的流行程度越来越高,越来越多的公司使用 Docker 打包和部署项目。但是也有很多公司只是追求新技术,将以前的单体应用直接打包为镜像,代码、配置方式等各方面保持不变,使用 Docker 后,并没有带来多大的便利,反而使得配置、启动过程变得更加繁杂,更难调试。
本章将讨论容器与 Pod 的关系,了解如何更好地将应用容器化。
## 什么是容器化应用
containerized applications 指容器化的应用,我们常常说使用镜像打包应用程序,使用 Docker 发布、部署应用程序,那么当你的应用成功在 Docker 上运行时,称这个应用是 containerized applications。
定义:
> **[Success] 定义**
>
> Containerized applications are bundled with their required libraries, binaries, and configuration files into a container.
>
> 容器化的应用程序与它们所需的库、二进制文件和配置文件绑定到一个容器中。
通常,容器都包含一个应用程序,以及正确执行二进制程序所需的依赖库、文件等,例如 Linux 文件系统+应用程序组成一个简单的容器。通过将容器限制为单个进程,问题诊断和更新应用程序都变得更加容易。与 VM(虚拟机)不同,容器不包含底层操作系统,因此容器被认为是轻量级的。Kubernentes 容器属于开发领域。
容器在操作系统之上,提供了 CPU、内存、网络、存储等资源的虚拟化,为应用在不同服务器里提供了一致的运行时环境。开发者可以通过容器创建一个可预测的环境,能够保证在开发、调试、生产时的环境都是一致的,减少开发团队和运维团队可以减少调试和诊断问题时,因环境差异带来的麻烦。同时,应用运行在一个沙盒中,对应用和系统进行了隔离,提高了安全性,还能限制应用程序使用的计算资源。
当然,并不是说能够将一个应用程序打包到容器中运行,就可以鼓吹产品;并不是每个应用程序都是容器化的优秀对象,例如在 DDD 设计中被称为大泥球的应用程序,具有设计复杂、依赖程度高、程序不稳定等确定,这种难以迁移、难以配置的应用程序明显是失败的产品。
在多年经验中,许多开发者对容器化技术进行了总结,这些强有力的经验、理论形成十二个云计算应用程序因素指导原则:
**1. Codebase:** One codebase tracked in revision control, many deploys
代码库: 一个代码库可以在版本控制和多份部署中被跟踪。一般使用 github 等对代码进行管理。
**2. Dependencies:** Explicitly declare and isolate dependencies
依赖项: 显式声明和隔离依赖项。
**3. Config:** Store config in the environment
配置:在环境中存储配置。
**4. Backing services:** Treat backing services as attached resources
支持服务:将支持服务视为附加资源(可拓展,而不是做成大泥球)。
**5. Build, release, run:** Strictly separate build and run stages
构建、发布、运行: 严格区分构建和运行阶段(连 Debug、Release 都没有区分的产品是真的垃圾)。
**6. Processes:** Execute the app as one or more stateless processes
过程:应用程序作为一个或多个无状态过程执行。
**7. Port binding:** Export services via port binding
端口绑定:可通过端口绑定服务对外提供服务。
**8. Concurrency**: Scale out via the process model
并发性:通过 process 模型进行扩展。
**9. Disposability:** Maximize robustness with fast startup and graceful shutdown
可处理性: 快速启动和完美关机,最大限度地增强健壮性。
**10. Dev/prod parity**: Keep development, staging, and production as similar as possible
开发/生产一致:尽可能保持开发中、演示时和生产时的相似性。
**11. Logs:** Treat logs as event streams
Logs:将日志视为事件流。
**12. Admin processes:** Run admin/management tasks as one-off processes
管理流程:将管理/管理任务作为一次性流程运行。
> 上述内容可能有笔者翻译不到位的地方,读者可阅读原文了解:[https://12factor.net/](https://12factor.net)
容器位于开发人员技能列表之中,开发人员需要掌握如何容器化应用。
另外,在一个产品中,好的容器化规范或方法,具有以下特点:
* 使用**声明式**的格式进行设置自动化,以最大限度地减少新开发人员加入项目的时间和成本;
* 与底层操作系统之间有一个**干净的契约**(资源隔离、统一接口),在执行环境之间提供**最大的可移植性**;
* 适合**部署**在现代**云平台上**,无需服务器和系统管理;
* **最大限度地减少**开发和生产之间的**差异**,实现**持续部署**以实现最大敏捷性;
* 并且可以在不对工具、架构或开发实践进行重大更改的情况下进行**扩展**。
在制作云原生应用的过程中,可以参考云计算应用程序因素指导原则,设计更加优秀的产品。
## Pod
最简单的说法就是将多个容器打包起来一起运行,这个整体就是 Pod。
> **[Info] 提示**
>
> 在上一章的 Docker 网络中,介绍了 container 网络模式,Pod 正是通过这种网络模式,让 Pod 中的容器共享网络,也就是说,Pod 中的容器,网络是互通的,容器之间不能使用相同的端口。
Pod 是 Kubernetes 集群中最小的执行单位。在 Kubernetes 中,容器不直接在集群节点上运行,而是将一个或多个容器封装在一个 Pod 中,接着将 Pod 调度到节点上运行,这些容器会一起被运行、停止,它们是一个整体。
Pod 中的所有容器共享相同的资源和本地网络,从而简化了 Pod 中应用程序之间的通讯。在 Pod 中,所有容器中的进程共享网络,可以通过 `127.0.0.1`、`localhost` 相互进行访问。详见 [3.1 章](../3.pod/1.pod.md) 中 "Pod 共享网络和存储" 一节。
一个简单的 Pod,其结构如下:
> **[Info] 提示**
>
> Pod 启动时会启动一个容器,K8S 给这个容器分配虚拟 IP,接着,其他容器使用 container 网络模式,连接到这个容器中,此时有容器共享网络。
随着 Pod 负载的增加,Kubernetes 可以自动复制 Pod 以达到预期的可拓展性(部署更多的 Pod 提供相同的服务,负载均衡)。因此,设计一个尽可能精简的 Pod 是很重要的,降低因复制扩容、减少收缩过程中带来的资源损失。
前面提到,容器应当是无状态的,所以拓展 Pod 时,每个实例都提供了一模一样的服务,这些 Pod 分配到不同的节点上,可以利用更多的 CPU、内存资源。
在第三章中,我们会更加详细地学习 Pod,这里就不再赘述。
## 容器与 Pod 的区别
容器包含执行特定流程或函数所需的代码(编译后的二进制可执行程序)。在 Kubernetes 之前,可以直接在物理或虚拟服务器上运行容器,但是缺乏 Kubernetes 集群所提供的可伸缩性和灵活性。
Pod 为容器提供了一种抽象,可以将一个或多个应用程序包装到一个 Pod 中,而 Pod 是 Kubernetes 集群中最小的执行单元。例如 Pod 可以包含初始化容器,这些容器为其它应用提供了准备环境,然后在应用程序开始执行前终结。Pod 是集群中复制的最小单位,Pod 中的容器作为整体被扩展或缩小。

例如对应前后端分离的项目,可能不需要把前端文件和后端程序放在一起,而是分别放在两个容器中。然后通过 Pod,将这两个容器作为一组服务打包在一起。
## 节点
Pod 是 Kubernetes 中最小的**执行单元**,而 Node 是 Kubernetes 中最小的计算**硬件单元**,节点可以是物理的本地服务器,也可以是虚拟机,节点即使宿主服务器,可以运行 Docker 的机器。
与容器一样,Node 提供了一个抽象层。多个 Node 一起工作形成了 Kubernetes 集群,它可以根据需求的变化自动分配工作负载,增加或减少在节点上的 Pod 数量。如果 A 节点和 B 节点的硬件资源是一致的,那么 A 、B 两个节点是等价的,如果 A 节点失败,它将自动从集群中移除,由 B 节点接管,不会出现问题。
每个节点都运行着一个名为 kubelet 的组件,它是节点的主要组件,Kubernetes 与集群控制平面组件(API Server)通信,所有对节点有影响的操作都会通过 kubectl 控制此节点。kubelet 也是 master 节点跟 worker 节点之间直接通讯的唯一组件。

kubelet 的一些功能有:
* 在节点上创建、更新、删除容器;
* 参与调度 Pod;
* 为容器创建和挂载卷;
* 使用命令查看 Pod 、容器,例如 `exec`、`log` 等时,需要通过 kubelet;
例如,集群有 A、B 两个节点,Pod 部署在哪里了,这不是用户关心的事情,用户在想看到容器的日志,可以随便找集群中的一台主机,执行命令,Kubernetes 会自动寻找容器所在的节点,然后kubectl 取得需要的内容。
另外节点上还有 proxy,主要是为 Pod 提供代理服务,外界可通过此代理,使用节点的 IP 访问 Pod 中的容器。

## 云原生
### 划分 Pod 和容器
在本小节中,笔者来介绍一下云原生方面的思想或知识,我们应该如何设计我们的应用。
容器中应只包含一个进程,或进程和创建的子进程。如果在同一个容器中包含多个进程,那么需要同时管理进程的启动、日志等,一个进程崩溃时,容易影响到另一个进程。由于多个进程都会记录信息到标准输出中(如控制台输出),容器日志会合在一起,可能会导致出现问题难以排查。
一个容器只应该运行一个进程,但是他们放到一个 Pod 中就行了吗?例如程序和数据库,在设计时应该放到同一个 Pod,还是单独不同的 Pod?接下来我们简单讨论一下这个问题,限于经验和技术水平,笔者的论点可能不到位,读者可以多参考一下别的文章,了解如何设计 这些架构。
下面以 Web 程序和数据库举例。
**耦合**
使用 Pod/容器 的原因,是为了让不同服务能够降低耦合,能够隔离环境,如果程序跟数据库放在一起,是否能够有足够的隔离程度?如果 Web 跟 数据库放在同一个 Pod,此时 web 跟数据库的实例(容器)数量是 1:1。对于 Kubernests 来说,Pod 是最小单位,Kbernetes 不能横向扩容单个容器,因此扩容的最小单位是 Pod,多个容器必须捆绑在一起。同时 Pod 中的所有容器都使用同一机器的资源。在同一个 Pod 中的容器,在生命周期、计算机资源(内存、CPU)、实例数量、网络等都会耦合在一起。
请参考 [https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/](https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/)
**访问压力**
一般来说,Web 是要被外界访问的,但是数据库为了安全,应当避免能够公网访问,只有处于集群中的程序或客户端才能访问数据库。同时Web的访问是直接面向用户的,访问量肯定比数据库的访问量大得多,而且数据库需要的存储空间比web大得多,那么两者使用的计算资源并不相近。
Pod 可以使用服务器资源,当服务器压力过大时,当太多用户访问 Web 时,Web就要考虑扩容实例,可以在其它节点上部署相同的 Pod(扩容),降低单节点访问压力。而一个数据库实例能够支持多个 Web 程序同时访问,那么数据库实例有必要跟 Web 放在同一个 Pod 中,保持 1:1的实例数量?
**故障恢复**
在 Kubernetes 中,容器应当是无状态的,也就是说容器或容器中的进程挂了,Kubernetes 可以快速在其它地方再创建一个 Pod ,启动容器,维持一定数量的 Pod 实例。对于 Web 来说,只要配置文件和数据库数据在,再启动一个 Web 容器,结果是一样的,流水的程序铁打的数据,只要数据在,可以随时启动 Web 程序,很容易恢复服务。但是数据库却不一定,数据库的运维比 Web 程序复杂得多,我们要考虑数据的安全性和可用性,当容器甚至节点服务器挂了后、磁盘损坏等,如何恢复数据库。数据库的维护不觉得。
两者的维护难度不在同一水平上,此时我们要考虑两者放在不同的 Pod 中。(实际上很少将数据库放在容器中,一般都是裸机部署)。
其中负载均衡是通过 Ingress 和 Service 实现的,后面的章节会学习到。
### 何时使用多个容器
前面提到 Web 跟数据库,应当划分在不同的 Pod 中,类似地,对于微服务中的不同服务或模块,也应当放在不同的 Pod 中。微服务架构、容器化,并不是那么容易,例如,对于前后端分离的项目,前后端文件放在同一个 容器中还是同一个 Pod 中还是不同 Pod 中?在设计中我们要考虑很多问题。
对于单体 Web 来说,一个程序中包含了所有服务,那么 Web 完全可以托管前端静态文件,前端文件跟后端程序打包在一起即可。例如 PHP、ASP.NET Core 等使用 wwwroot、www 等 目录存储静态文件。
如果是一个较大的网站,网站使用了多个微服务,则前端更可能放到一个 Pod 中,用户访问前端页面,然后前端根据访问的模块,自动访问不同的服务。
如果前端和后端文件需要频繁发布,两者的发布版本分开工作,则为了避免一方等待另一方发布,或者从 Devops 角度,前端和和后端文件可以放在不同容器中,然后通过存储卷,两个容器共享文件。
如果一个 Pod 中,包含一个主进程和多个辅助进程,则可以使用一个 Pod 部署多个 容器,多个容器之间紧密联系。
具体怎么设计,需要根据实际情况考虑。
--------------------------------
标题:1.5 Kubernetes 入门基础
来源:/zh/kubernetes/1.basic/5.k8s
# 1.5 Kubernetes 入门基础
我们要学习 Kubernetes,就有首先了解 Kubernetes 的技术范围、基础理论知识库等,要学习 Kubernetes,肯定要有入门过程,在这个过程中,学习要从易到难,先从基础学习。
接下来笔者将为大家讲解 Kubernetes 各方面的知识,让读者了解 Kubernetes 是什么。
## Kubernetes 是什么
在 2008 年,**LXC(Linux containers)** 发布第一个版本,这是最初的容器版本;2013 年,Docker 推出了第一个版本;而 Google 则在 2014 年推出了 **LMCTFY**。
为了解决大集群(Cluster)中容器部署、伸缩和管理的各种问题,出现了 Kubernetes、Docker Swarm 等软件,称为 **容器编排引擎**。
容器的产生解决了很多开发、部署痛点,但随着云原生、微服务的兴起,纯 Docker 出现了一些管理难题。我们先思考一下,运行一个 Docker 容器,只需要使用 `docker run ...` 命令即可,这是相当简单(relatibely simple)的方法。
但是,要实现以下场景,则是困难的:
* 跨多台主机的容器相互连接(connecting containers across multiple hosts)
* 拓展容器(scaling containers)
* 在不停机的情况下配置应用(deploying applications without downtime)
* 在多个方面进行服务发现(service discovery among several aspects)
Kubernetes 是 Google 基于十多年的生产环境运维经验,开发出的一个生产级别的容器编排系统。在 Kunernetes 文档中,这样描述 Kubernetes:
> **[Success]**
>
> "an open-source system for automating deployment, scaling, and management of containerized applications".
>
> “一个自动化部署、可拓展和管理容器应用的开源系统”
Google 的基础设施在虚拟机(Virtual machines)技术普及之前就已经达到了很大的规模,高效地使用集群和管理分布式应用成为 Google 挑战的核心,而容器技术提供了一种高效打包集群的解决方案。
多年来,Google 一直使用 Borg 来管理集群中的容器,积累了大量的集群管理经验和运维软件开发能力,Google 参考 Borg ,开发出了 Kubernetes,即 Borg 是 Kubernetes 的前身。(但是 Google 目前还是主要使用 Borg)。
Kubernetes 从一开始就通过一组基元(primitives)、强大的和可拓展的 API 应对这些挑战,添加新对象和控制器地能力可以很容易地地址各种各样的产品需求(production needs)。
编排管理是通过一系列的监控循环控制或操作的;每个控制器都向询问对象状态,然后修改它,直至达到条件为止。容器编排是管理容器的最主要的技术。Dockers 也有其官方开发的 swarm 这个编排工具,但是在 2017 年的容器编排大战中,swarm 败于 Kubernetes。
### Kubernetes 集群的组成
在 Kubernets 中,运行应用程序的环境处于虚拟化当中,因此我们一般不谈论硬件。
我们谈起 Kubernetes 和应用部署时,往往会涉及到容器、节点、Pods 等概念,它们共同工作来管理容器化(containerized)应用的部署和执行,但是各种各样的术语,令人眼花缭乱。为了更好地摸清 Kubernetes,下面我们将列举这些有边界的对象。
| 成分 | 名称 |
| :----------------------- | :----------- |
| Cluster | 集群 |
| Node | 节点 |
| Pod | 不翻译 |
| Container | 容器 |
| Containerzed Application | 容器化的应用 |
在 Kubernetes 中,不同的对象其管理的范围、作用范围不同,它们的边界大小也不同。接下来的内容,按将从小到大的粒度介绍这些组成成分。
*Pod*
在上一章中已经介绍过,Pod 是 Kubernetes 中管理和调度的最小工作单位,Pod 中可以包含多个容器。这些容器会共享 Pod 中的网络等资源。当部署 Pod 时,会把一组关联性较强的容器部署到同一个节点上。

而节点则是指一台服务器、虚拟机等,运行着一个完整的操作系统,提供了 CPU、内存等计算资源,一个节点可以部署多个 Pod。
而一个集群(Cluster)之中,运行着 N 台服务器,即 N 个节点。这些节点有两种,一种是 master 节点,一种是 worker 节点。master 节点运行着 Kubernetes 系统组件,而 worker 节点负责运行用户的程序。所有节点都归 master 管,我们通过命令、API 的方式管理 Kubernetes 集群时,是通过发送命令或请求到 master 节点上的系统组件,然后控制整个集群。
另外,kubernetes 中有命名空间(namespace)的概念,这跟在 1.2 章中学习到的 Linux-namespace 类似,在一个集群中使用命名空间将不同的 Pod 隔离开来。但是 Kubernetes 中,不同 namespace 的 Pod 是可以相互访问的,它们不是完全隔离的。
### Kubernetes 结构
用图来表示体系结构,是阐述 Kubernetes 最快的方式,下面是一张称为 _Kubernetes Architecture_ graphic 。

上图是简单的 kubernetes 结构,左侧虚线方框中,是 master 节点,运行着各种各样的组件,master 节点负责控制整个集群,当然在很大的集群中也可以有多个 master 节点;而右侧是三个工作节点,负责运行我们的容器应用。这种结构一般称为 master-slave 结构,因为某些原因,在 Kubernetes 中后来改称为 master-minions。工作节点挂了没关系,master 节点会将故障节点上的业务自动在另一个节点上部署。
工作节点比较简单,在工作节点中,我们看到有 kubelet 和 kube-proxy 两个组件,这两个组件在上一章中接触过了,kubelet 和 kube-proxy 都是跟 主节点的 kube-apiserver 进行通信的。kube-proxy 全称是 Kubenetes Service Proxy,负责组件之间的负载均衡网络流量。
在上图中, 主节点由多个组件构成,结构比较复杂, 主节点中记录了整个集群的工作数据,负责控制整个集群的运行。工作节点挂了没关系,但是 主节点挂了,整个集群就挂了。因此, 有条件的情况下,也应该 设置多个 主节点。
一个 主节点中包含以下访问:
* 一个 API 服务(kube-apiserver)
* 一个调度器(kube-scheduler)
* 各种各样的控制器(上图有两个控制器)
* 一个存储系统(这个组件称为etcd),存储集群的状态、容器的设置、网络配置等数据。
这张图片中还有很多东西,这里暂时不作讲解,我们在后面的章节再去学习那些 Kubernetes 中的术语和关键字。
### 组件
一个 kubernetes 集群是由一组被称为节点的机器或虚拟机组成,节点有 master、worker 两种类型。一个集群中至少有一个 master 节点,在没有 worker 节点的情况下, Pod 也可以部署到 master 节点上。如果集群中的节点数量非常多,则可考虑扩展 master 节点,使用多个 master 节点控制集群。
在上一小节中,我们看到 主节点中包含了比较多的组件,工作节点也包含了一些组件,这些组件可以分为两种,分别是 Control Plane Components(控制平面组件)、Node Components(节点组件)。
**Control Plane Components** 用于对集群做出全局决策,部署在 master 节点上;
**Node Components** 在 worker 节点中运行,为 Pod 提供 Kubernetes 环境。
## Master 节点
Master 是由一组称为控制平面组件组成的,如果你已经根据第二章中,通过 minikube 或 kubeadm 部署了 kubernetes,那么我们可以打开 `/etc/kubernetes/manifests/` 目录,这里存放了 k8s 默认的控制平面组件的 YAML 文件。
```
.
├── etcd.yaml
├── kube-apiserver.yaml
├── kube-controller-manager.yaml
└── kube-scheduler.yaml
```
对于集群来说, 这四个组件都是是必不可少的。

在结构图中,还有一个 cloud-controller 组件,主要由云平台服务商提供,属于第三方组件,这里不再讨论。下面我们来了解 master 中的组件。
master 节点中各个组件(控制平面组件)需要使用到的端口:
| 协议 | 方向 | 端口范围 | 作用 | 使用者 |
| ---- | ---- | --------- | ----------------------- | ---------------------------- |
| TCP | 入站 | 6443 | Kubernetes API 服务器 | 所有组件 |
| TCP | 入站 | 2379-2380 | etcd 服务器客户端 API | kube-apiserver, etcd |
| TCP | 入站 | 10250 | Kubelet API | kubelet 自身、控制平面组件 |
| TCP | 入站 | 10251 | kube-scheduler | kube-scheduler 自身 |
| TCP | 入站 | 10252 | kube-controller-manager | kube-controller-manager 自身 |
普通节点中各个组件需要使用到的端口:
| 协议 | 方向 | 端口范围 | 作用 | 使用者 |
| ---- | ---- | ----------- | -------------- | -------------------------- |
| TCP | 入站 | 10250 | Kubelet API | kubelet 自身、控制平面组件 |
| TCP | 入站 | 30000-32767 | NodePort 服务† | 所有组件 |
### kube-apiserver
kube-apiserver 是 k8s 主要进程之一,apiserver 组件公开了 Kubernetes API (HTTP API),apiserver 是 Kubernetes 控制面的前端,我们可以用 Go、C# 等编程语言写代码,远程调用 Kubernetes,控制集群的运行。apiserver 暴露的 endiont 端口是 6443。
为了控制集群的运行,Kubernetes 官方提供了一个名为 kubectl 的二进制命令行工具,正是 apiserver 提供了接口服务,kubectl 解析用户输入的指令后,向 apiserver 发起 HTTP 请求,再将结果反馈给用户。
> **[Info] kubectl**
>
> kubectl 是 Kubernetes 自带的一个非常强大的控制集群的工具,通过命令行操作去管理整个集群。
Kubernetes 有很多可视化面板,例如 Dashboard,其背后也是调用 apiserver 的 API,相当于前端调后端。
总之,我们使用的各种管理集群的工具,其后端都是 apiserver,通过 apiserver,我们还可以定制各种各样的管理集群的工具,例如网格管理工具 istio。腾讯云、阿里云等云平台都提供了在线的 kubernetes 服务,还有控制台可视化操作,也是利用了 apiserver。
### etcd
etcd 是兼具一致性和高可用性的键值数据库,作为保存 Kubernetes 所有集群数据的后台数据库。apiserver 的所有操作结果都会存储到 etcd 数据库中,etcd 主要存储 k8s 的状态、网络配置以及其它持久化数据,etcd 是使用 B+ 树实现的,etcd 是非常重要的组件,需要及时备份数据。
### kube-scheduler
scheduler 负责监视新创建的 pod,并把 pod 分配到节点上。当要运行容器时,发送的请求会被调度器转发到 API;调度器还可以寻找一个合适的节点运行这个容器。
### kube-controller-manager
kube-controller-manager 中包含了多个控制器,它们都被编译到一个二进制文件中,但是启动后会产生不同的进程。这些控制器有:
* 节点控制器(Node Controller)
负责在节点出现故障时进行通知和响应
* 任务控制器(Job controller)
监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成
* 端点控制器(Endpoints Controller)
填充端点(Endpoints)对象(即加入 Service 与 Pod)
* 服务帐户和令牌控制器(Service Account & Token Controllers)
为新的命名空间创建默认帐户和 API 访问令牌
控制器控制的 Pod、Job、Endpoints、Service 等,都是后面要深入学习的。
--------------------------------
标题:导读
来源:/zh/kubernetes/2.deploy/README
# 第二章:部署和配置
本章内容主要是介绍、实践部署以及配置 Kubernetes 集群,如果读者的服务器环境处于国内,可能会因为网络原因无法部署 Kubernetes,建议读者使用国产的容器平台管理工具 kubesphere。
但是还是建议读者使用本章中的 minikube 和 kubeadm 教程部署 kubernetes,因为教程中会讲解一些 kubenetes 的知识,我们要学习它,就不应该绕过这个部署过程。如果是新手上路,部署失败,则建议使用 kubesphere 一键部署,等学习过 kubernetes 后,有空再尝试手动部署 kubernetes。
## 学习目标
* 下载安装和配置工具
通过多种方式下载安装工具集,了解每种工具的功能。
* 安装一个 Kubernetes 主节点并扩展一个集群
搭建 Kubernetes Master 节点,并加入 Worker 节点。
* 解决网络问题
解决国内无法拉取 Kubernetes 镜像问题。
* 部署和配置
学会部署以及配置启动集群,学会清除集群环境。
对于 Kubernetes CKAD 认证来说,在部署的时候需要考虑以下几个问题:
* 配置安全通信的网络方案
例如 Claio。
* 讨论高可用性部署注意事项
本章只讨论部署相关的知识,关于网络和其它知识,在其它章节中可以学习到。
## 如何创建练习环境
可以使用 minikube 做单机学习环境,或者购买云服务器、白嫖 3个月 Google Cloud ,也可以使用电脑创建多个虚拟机或者多台电脑来搭建节点,或者使用线上学习环境。
对于每个节点或虚拟机,尽量满足最低每台 2核2GB 的配置。
在 [https://katacoda.com/](https://katacoda.com) 网站,有很多教程以及能够免费学习,并且可以使用线上的服务器环境。
--------------------------------
标题:2.1 使用 Minikube 部署
来源:/zh/kubernetes/2.deploy/1.minikube
# 2.1 使用 Minikube 部署
Minikube 是一个创建单机 Kubernetes 集群的 工具,它可以在一台服务器上快速创建一个学习环境,单机集群也可以学习到大多数入门的 Kubernetes 知识以及上手练习。CKAD 认证并不要求掌握 Minikube,不过我们可以初步学习练习,后面再使用 kubeadm 部署多节点集群。
本篇内容较为简单,可供读者练手使用,除了会使用 minikube 部署 kubernetes 外,也会部署应用,对于本章的内容,读者可快速练习一遍,后面再详细介绍各方面的知识。
## Minikube
Minikube 是一个二进制工具,项目源码或文档等内容可以在 [https://github.com/kubernetes/minikube](https://github.com/kubernetes/minikube) 中找到,已经编译好的二进制可执行文件,可以在仓库的 Release 中下载,Minikube 支持 Windows 、Linux、MacOS。
直接下载 minikube 最新版本的二进制文件。
可以通过官方 Github 仓库下载,也可以使用国内代理下载。
下载地址(Linux版本):
* [https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.20.0/minikube-linux-amd64](https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.20.0/minikube-linux-amd64)
* [https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64](https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64)
注:如果要下载 Win 版本,把 `minkube-linux-amd64` 改成 `minkube-windows-amd64.exe` 即可;如果是 MacOS 则是 `minikube-darwin-amd64`。另外要注意下载的版本号 。
阿里云源下载的二进制工具,本身可以使用国内镜像,不需要代理,可以到仓库了解 [https://github.com/AliyunContainerService/minikube](https://github.com/AliyunContainerService/minikube)。
你也可以看官方文档,按照文档安装 [https://minikube.sigs.k8s.io/docs/start/](https://minikube.sigs.k8s.io/docs/start/) 。
```bash
curl -Lo minikube {下载地址}
```
```bash
chmod +x minikube
```
```bash
sudo mv minikube /usr/local/bin
```
### 部署
直接执行 `minikube start` 命令即可进行部署,但是国内会被墙,可能拉取不了镜像,需要设置代理,可参考 [2.4 章设置镜像代理](./4.kubeadm_prox.md) 。
```bash
# 国外服务器
minikube start
# 使用阿里云版本时,指定国内源
minikube start --image-mirror-country=cn
# 使用阿里云版本时,指定镜像源
minikube start --image-mirror=registry.cn-hangzhou.aliyuncs.com/google_containers
```
注:虚拟机安装或其他方式,需要配置安装驱动,请参考官方文档。还是无法拉取镜像的话,打开文档看看 [https://minikube.sigs.k8s.io/docs/handbook/vpn_and_proxy/](https://minikube.sigs.k8s.io/docs/handbook/vpn_and_proxy/) ,配置代理试试。
接下来 minikube 会拉取各种镜像,需要一些时间。
```bash
* Pulling base image ...
* Downloading Kubernetes v1.20.2 preload ...
> preloaded-images-k8s-v10-v1...: 491.71 MiB / 491.71 MiB 100.00% 60.04 Mi
> gcr.io/k8s-minikube/kicbase...: 357.67 MiB / 357.67 MiB 100.00% 7.41 MiB
* Creating docker container (CPUs=2, Memory=4000MB) .../
```
通过 `minikube version` 命令可以查看 minikube 的版本,接下来我们使用 `minikube start` 命令,可以直接创建一个 kubernetes 集群。
**问题一**
如果启动不起来提示没有 docker 用户,这是因为默认不应该使用 root 用户执行命令和启动程序,可以创建一个 docker 用户,也可以使用 `--driver=none` 指定不用 docker 用户。
如果要用 docker 用户:
```bash
groupdel docker
useradd -m docker
passwd docker
# 修改密码后,加入用户组
gpasswd -a docker docker
```
打开 `/etc/sudoers` 文件,在 `root ALL=(ALL:ALL) ALL` 下 增加新的一行:
```bash
docker ALL=(ALL)ALL
```
然后切换为 docker 用户:`su docker` 。
如果不用 docker 用户,只需要在初始化集群时加上 `--driver=none` 。
```bash
minikube start --driver=none
```
**问题二**
PS:如果报 `X Exiting due to GUEST_MISSING_CONNTRACK: Sorry, Kubernetes 1.20.2 requires conntrack to be installed in root's path`,则需要安装 constrack ,`apt install conntrack`。
如果没有问题,会自动进行安装。

minikube 完成初始化后,打开新的终端窗口,执行 `minikube dashboard` 启动面板,根据 URL 地址,可以访问面板。
正常的话,执行 `docker ps` 后是这样的。

### 查看集群状态
本身 minikube 带有一些简单的 `kubectl` 命令,可以查看集群状态信息。
获取集群所有节点(机器):
```bash
minikube kubectl get nodes
```
获取集群所有命名空间:
```bash
minikube kubectl get namespaces
```
查看集群所有 Pod:
```bash
minikube kubectl -- get pods -A
```

## 创建资源
由于 minikube 不会自动下载 kubectl、kubelet 等工具,我们需要手动安安装,你可以参考 2.2 章的安装方法, 关于 kubectl 、kubelet ,后面的章节会详细介绍。最简单的安装方法:
```bash
# 仅供 ubuntu 参考
snap install kubectl --classic
snap install kubelet --classic
```
本节的内容供简单练习,不会详细说明命令的作用和相关知识,待读者阅读后面的章节时,可以学习到更多内容。
### 创建 Deployment
Deployment 可以部署应用并管理实例数量,它提供了一种故障的自我修复机制,当应用挂了后,Deployment 可以自动启动一个新的实例,维护固定数量的 Pod。
`kubectl create deployment`命令创建管理 Pod 的 Deployment。
```bash
# 格式 kubectl create deployment {deployment名称} {参数}
kubectl create deployment hello --image=nginx:latest
```
查看 Deployment:
```bash
kubectl get deployments
```
```bash
NAME READY UP-TO-DATE AVAILABLE AGE
hello 1/1 1 1 6m39s
```
查看 Pod :
```bash
kubectl get pods
```
```bash
NAME READY STATUS RESTARTS AGE
hello-b8d95ff4c-x67zr 1/1 Running 0 7m8s
```
查看集群事件:
```bash
kubectl get events
```
查看 `kubectl` 配置:
```bash
kubectl config view
```
### 创建 Service
Service 为 Pod 提供了一种外网访问能力,默认情况下,Pod 只能在 Kubernetes 集群的**同一节点访问**,如果要外部网络访问,则需要为 Pod 暴露一个 Kubnetes Service,Service 为 Pod 提供了外网访问能力。这里我们把上一小节的 hello 暴露出去。
nginx 镜像会暴露一个 80 端口,通过 80 端口我们可以访问到 nginx 服务。但是在 Kubernetes 中,则可能有些麻烦。
每个 Pod 在集群中都有一个唯一 IP,我们可以查看详细的 Pod 信息:
```bash
kubectl get pods -o wide
```

我们可以直接访问它:
```bash
curl 172.17.0.3
```

为了能够在外网访问,我们创建 Service:
```bash
kubectl expose deployment hello --type=LoadBalancer --port=80
```
然后查看刚刚创建的 service:
```bash
kubectl get service hello
# 或
minikube service hello
```
```bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello LoadBalancer 10.102.73.188 80:31286/TCP 5s
```
```bash
|-----------|-------|-------------|-------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|-------|-------------|-------------------------|
| default | hello | 80 | http://10.170.0.5:31286 |
|-----------|-------|-------------|-------------------------|
* Opening service default/hello in default browser...
http://10.170.0.5:31286
```
此时,在集群内,通过 `http://10.170.0.5:31286` 可以访问此 Pod,或者在外网访问 31286 端口。
### 清理集群资源
由于 Minikube 创建的资源只是单机的,同时会产生很多 Docker 容器,我们练习完毕后,就要清除环境,以免影响后续实践环境。
首先清除 service、deployment (可以跳过这个步骤)。
```bash
kubectl delete service hello
kubectl delete deployment hello
```
然后停止 Minikube 虚拟机(VM):
```bash
minikube stop
```
接着删除 Minikube 虚拟机(VM):
```bash
minikube delete
```
--------------------------------
标题:2.2 使用 kubeadm 部署
来源:/zh/kubernetes/2.deploy/2.kubeadm
# 2.2 使用 kubeadm 部署
上一章中,我们用 minikube 去搭建单机集群,并且创建 Deployment、Service(在三章中讲解),本篇将介绍利用 kubeadm 部署多节点集群,并学会 安装以及使用 kubernetes 的命令行工具,快速创建集群实例,完成部署 hello world 应用的实践。
Kubeadm 是 CKAD 认证中要求掌握的部署方式,但是镜像需要国外网络才能下载,读者如果是国内服务器,可以参考 2.4 章的内容,使用国内服务器进行代理。
本章内容主要介绍如何安装 kubeadm 以及部署集群、添加节点。
需要提前在服务器安装好 Docker。
### 命令行工具
在 kubernetes 中,主要有三个日常使用的工具,这些工具使用 kube 前缀命名,这三个工具如下:
* `kubeadm`:用来初始化集群的指令,能够创建集群已经添加新的节点。可用其它部署工具替代。
* `kubelet`:在集群中的每个节点上用来启动 Pod 和容器等,每个节点必须有,相对于节点与集群的网络代理。
* `kubectl`:用来与集群通信/交互的命令行工具,与 kubernetes API-Server 通讯,是我们操作集群的客户端。
在 1.5 章中介绍过 kubelet、kubectl,kubelet 负责集群中节点间的通讯,kubectl 供用户输入命令控制集群,而且 kubeadm 则是创建集群、添加减少节点的工具。
## 安装命令行工具
命令行工具是每个节点都需要安装的, kubectl、kubelet 两个是必需的组件,而 kubeadm 则可以被代替。kubeadm 是 Kubenetes 官方推荐的部署工具,但由于网络等各方面原因,中文社区中也开发了一些替代项目,例如 Kubesphere([https://kubesphere.com.cn/](https://kubesphere.com.cn)),可在国内部署 Kubernetes,省去网络问题。
### 通过软件仓库安装
下面介绍如何 通过 Google 的源下载安装工具包。
更新 `apt` 包索引并安装使用 Kubernetes `apt` 仓库所需要的包:
```bash
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
```
下载 Google Cloud 公开签名秘钥:
```bash
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
```
添加 Kubernetes `apt` 仓库:
```bash
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
```
注:如果是国内服务器,请忽略以上两步,使用以下命令解决:
```bash
apt-get update && apt-get install -y apt-transport-https
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
cat </etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
```
更新 `apt` 包索引,安装 kubelet、kubeadm 和 kubectl,并锁定其版本:
```bash
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
```
执行命令检查是否正常:
```bash
kubeadm --help
```
### 不同操作系统
只是这里介绍一下 ubuntu 和 centos 不同的安装方法,已经通过前面的安装方法安装好,则不需要理会这一小节。
Ubuntu 和 Debain 等系统可以使用以下命令通过软件仓库安装:
```bash
sudo apt-get update && sudo apt-get install -y apt-transport-https gnupg2 curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
```
Centos、RHEL 等系统可以使用以下命令通过软件仓库安装:
```bash
cat < /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 -y kubelet kubeadm kubectl
```
## 集群管理
### 创建 kubernetes 集群
Kubeadm 是一个创建管理工具,主要提供了 `kubeadm init` 和 `kubeadm join` 两个命令,作为创建 Kubernetes 集群的 “快捷途径” 的最佳实践。
Kubernetes 集群由 Master 和 Worker 两种节点组成,Master 节点负责控制集群所有的节点。
注意,本教程集群中的节点应当都是内网可互通的服务器,这些服务器之间可以通过内网相互访问。如果是服务器之间通过公网相互通讯的,操作方法请查询其它教程。
**1,创建 Master**
执行 `hostname -i` 查看此 node 的 ip。
我们初始化一个 API Server 服务,绑定地址为 192.168.0.8(按照你的ip改)。此步骤创建了一个 master 节点。
注:可以直接使用 `kubeadm init`,它会自动使用默认网络ip。
```bash
kubeadm init
# 或 kubeadm init --apiserver-advertise-address 192.168.0.8
# 或 kubeadm init --apiserver-advertise-address $(hostname -i)
```
> 部署失败,可以参考下面两个命令,查看失败原因。
>
> ```
> systemctl status kubelet
> journalctl -xeu kubelet
> ```
>
>
> 常见与 Docker 有关的错误可参考:
> https://kubernetes.io/docs/setup/production-environment/container-runtimes/#docker
完成后,会提示一些信息,在提示的内容中找到:
```bash
kubeadm join 192.168.0.8:6443 --token q25z3f.v5uo5bphvgxkjnmz \
--discovery-token-ca-cert-hash sha256:0496adc212112b5485d0ff12796f66b29237d066fbc1d4d2c5e45e6add501f64
```
保存这段信息下来备用,后面加入节点时需要使用到。
如果有提示 `Alternatively, if you are the root user, you can run:`则你还需要执行下面的命令。
```bash
export KUBECONFIG=/etc/kubernetes/admin.conf
```
> **[Info] 提示**
>
> admin.conf 是连接 Kubernetes 的认证文件,通过此文件才能连接到 kubernetes,kubectl 也需要这个文件;在 Linux 中,使用 KUBECONFIG 环境变量知道认证文件的所在。
>
> Linux 中每个用户的环境变量是不同的,如果切换了用户,则也需要设置 KUBECONFIG 环境变量;如果要在别的节点上连接集群,则可以把这个文件复制过去。
后面的操作都需要 admin.conf 文件,否则会报 `The connection to the server localhost:8080 was refused - did you specify the right host or port?` 。
由于 `export` 的环境变量不能持久化,请打开 `~/.bashrc` 文件,把这个命令加到文件最后面。
> **[Info] 提示**
>
> 为了保护 /etc/kubernetes/admin.conf,避免直接指向,建议每个用户复制一次此文件到用户目录下,其命令如下:
>
> ```bash
> mkdir -p $HOME/.kube
> cp -f /etc/kubernetes/admin.conf $HOME/.kube/config
> chown $(id -u):$(id -g) $HOME/.kube/config
> ```
**2,初始化网络**
这一步不是必需的,不过一般来说,部署 Kubernetes 会配置网络,否则会节点之间不能相互访问,读者可以跟着做一次,在后面的章节中我们在一探究竟。
通过远程配置文件初始化网络,需要从第三方拉取一个 yaml 文件。
```bash
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')" --namespace=kube-system
# --namespace=kube-system 表示插件放到 kube-system 命名空间中运行
```
成功的话会提示:
```bash
serviceaccount/weave-net created
clusterrole.rbac.authorization.k8s.io/weave-net created
clusterrolebinding.rbac.authorization.k8s.io/weave-net created
role.rbac.authorization.k8s.io/weave-net created
rolebinding.rbac.authorization.k8s.io/weave-net created
daemonset.apps/weave-net created
```
我们也可以手动配置,执行 `kubectl version` 查看版本号,找到 `GitVersion:v1.21.1` ,替换 yaml 文件的地址 `https://cloud.weave.works/k8s/net?k8s-version=v1.21.1`,然后执行 `kubectl apply -n kube-system -f net.yaml` 即可。
**3,加入集群**
前面已经创建了 Master 节点,接下来将另一个服务器以 Worker 节点的方式加入集群中。如果读者只有一台服务器,则可以跳过这个步骤。
当节点加入 kubeadm 初始化的集群时,双方需要建立双向信任,分为发现(Worker信任Master) 和 TLS 引导(Master信任待加入Worker)两部分。目前有两种加入方式,一种是通过令牌加入,一种是通过 kubeconfig 文件加入。
格式:
```bash
kubeadm join --discovery-token abcdef.xxx {IP}:6443 --discovery-token-ca-cert-hash sha256:xxx
kubeadm join--discovery-file file.conf
```
在第二个节点中,使用之前**备份好的命令**,直接执行,加入集群,**格式如下命令所示**。
```bash
kubeadm join 192.168.0.8:6443 --token q25z3f.v5uo5bphvgxkjnmz \
--discovery-token-ca-cert-hash sha256:0496adc212112b5485d0ff12796f66b29237d066fbc1d4d2c5e45e6add501f64
```
复制粘贴时,要注意,可能会由于 `\` 换行符,导致粘贴时,多了一个小数点,导致报错。

### 可能碰到的问题
查看 docker 版本:`yum list installed | grep docker` 和 `docker version`。
如果部署过程中出现 `failed to parse kernel config: unable to load kernel module`,也说明了 docker 版本太高,需要降级。
如果服务器装了 dnf,那么降级 docker 版本的命令:
```
dnf remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
```
```
dnf -y install dnf-plugins-core
```
```
dnf install docker-ce-18.06.3.ce-3.el7 docker-ce-cli containerd.io
```
不行的话就按照 [https://docs.docker.com/engine/install/centos/](https://docs.docker.com/engine/install/centos/) 降级,或者自行按照其它方法处理。
注意,`docker version` 会看到 client 和 server 版本,两者的版本号可能不一致。
### 删除节点
在生产环境中,由于节点上已经部署着服务,因此直接删除节点,可能会导致严重的故障问题。因此需要移除一个节点时,首先要在此节点上驱逐所有 Pods,Kubernetes 会自动将此节点上的 Pod 转移到其它节点上部署(第三章会讲)。
获取集群中的所有节点,找到需要驱逐的节点名称。
```bash
kubectl get nodes
```
驱逐此节点上所有的 Pod:
```bash
kubectl drain {node名称}
```
虽然驱逐了节点上所有的服务,但是节点依然在集群中,只是 Kubernetes 不会再部署 Pod 到此节点上。如果需要恢复此节点,允许继续部署 Pod,可使用:
```bash
kubectl uncordon {节点名称}
```
关于驱逐,后面的章节会学习到。
注:驱逐 Pod,并一定能够驱逐所有 Pod,有些 Pod 可能不会被清除。
最终删除此节点:
```bash
kubectl delete node {节点名称}
```
集群删除了此节点后,节点上还保留着一些数据,可以继续清除环境。
### 清除环境
如果步骤做错了想重来,或者移除节点需要清除环境,可以执行 `kubeadm reset [flags]` 命令。
注:只执行 `kubeadm reset` 命令无效。
`[flags]` 有四种类型:
```bash
preflight Run reset pre-flight checks
update-cluster-status Remove this node from the ClusterStatus object.
remove-etcd-member Remove a local etcd member.
cleanup-node Run cleanup node.
```
我们需要执行:
```bash
kubeadm reset cleanup-node
kubeadm reset
```
即可在当前服务器上清除 Kubernetes 残留的 容器或者其它数据。
--------------------------------
标题:2.3 CKAD认证中的部署教程
来源:/zh/kubernetes/2.deploy/3.kubeadm_ckad
# 2.3 CKAD认证中的部署教程
在上一章中,我们已经学会了使用 kubeadm 创建集群和加入新的节点,在本章中,将按照 CKAD 课程的方法重新部署一遍,实际上官方教程的内容不多,笔者写了两篇类似的部署方式,如果已经部署了 kubernetes 集群,则本章的内容可跳过。
## 部署
### 预设网络
本节主要是配置 hosts 文件,在后续配置中,通过主机名称即可快速连接,而不需要每次都打上 IP 地址。
我们在 Master 节点服务器执行 `ip addr` 命令,找到 `ens4`,把里面提到的 ip 记录下来。
```bash
ens4: mtu 1460 qdisc mq state UP group default qlen 1000
link/ether 42:01:0a:aa:00:02 brd ff:ff:ff:ff:ff:ff
inet 10.170.0.2/32 scope global dynamic ens4
valid_lft 2645sec preferred_lft 2645sec
inet6 fe80::4001:aff:feaa:2/64 scope link
valid_lft forever preferred_lft forever
```
如上述 ip 是 10.170.0.2。或者使用 `hostname -i` 查询。方式有很多,目前是获得主机的内网 IP。
然后修改 `/etc/hosts` 文件,加上一行(替换这个ip为你的):
```bash
10.170.0.2 k8smaster
```
后面我们访问集群,使用 k8smaster 这个主机名称(域名),而且不是需要 IP 地址,使用主机名称方便记忆,也避免了 IP 强固定。
### kubeadm 安装 k8s
这里的部署过程跟上一章中的有所差异,因为上章中,直接使用 `kubeadm init` 进行初始化集群,没有配置更多细节。
执行 `kubectl version` 查看 k8s 版本,找到这段`GitVersion:"v1.21.0"` ,即为 Kubernetes 版本。
创建一个 kubeadm-config.yaml 文件,我们使用 `kubeadm init` 时,通过此配置文件出初始化 k8s master。
文件内容为:
```yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubenetesVersion: 1.21.0
controlPlaneEndpoint: "k8smaster:6443"
networking:
podSubnet: 192.168.0.0/16
```
注意,`:` 后面必须带一个空格。表示`key: value`。例如 `image: nginx:letest` ,不带空格的 `:` 会连在一起。
然后通过配置文件初始化 Master:
```bash
kubeadm init --config=kubeadm-config.yaml --upload-certs --v=5 | tee kubeadm-init.out
# 可省略为 kubeadm init --config=kubeadm-config.yaml --upload-certs
```
`--v=5` 可以输出更多信息信息,`tee xxx` 可以让信息输出到一个文件中,方便收集日志或者后续检查。
执行初始化命令后,终端或查看 `kubeadm-init.out` 文件,有以下内容:
```bash
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of the control-plane node running the following command on each as root:
kubeadm join k8smaster:6443 --token 45td1j.xqdscm4k06a4edi2 \
--discovery-token-ca-cert-hash sha256:aeb772c57a35a283716b65d16744a71250bcc25d624010ccb89090021ca0f428 \
--control-plane --certificate-key d76287ccc4701db9d34e0c9302fa285be2e9241fc43c94217d6beb419cdf3c52
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join k8smaster:6443 --token 45td1j.xqdscm4k06a4edi2 \
--discovery-token-ca-cert-hash sha256:aeb772c57a35a283716b65d16744a71250bcc25d624010ccb89090021ca0f428
```
按照提示,我们**逐个执行**下面的命令,不要一次性粘贴执行,因为 `cp -i` 表示要你输入 `y/n` 确认更改,一次性粘贴会导致跳过(把 -i 改为 -f 也行)。
```
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```
然后:
```bash
export KUBECONFIG=/etc/kubernetes/admin.conf
```
笔者注:`KUBECONFIG` 环境变量在下次登录或新建终端窗口会失效,打开 用户目录的`.bashrc` 文件,在最后面加上 `export KUBECONFIG=/etc/kubernetes/admin.conf` ,可保证下次登录或切换终端,依然可用。
笔者注:因为涉及到多用户,所以如果切换用户,就不能使用 `kubeadm/kubectl/kubelet` 命令了,如果读者切换了用户,则可以执行上面 `make -p $HOME/.kube` 到 `export xxx` 这两部分的命令,这样别的用户也可以执行命令操作节点。
输入 `kubeadm config print init-default` 可以查看到 master 初始化时配置。
以上便是 CKAD 官方的部署方法。
## 配置 Calico
#### 什么是 CNI
CNI 意为容器网络接口,是 Kubernetes 的一种标准设计,使用者可以不需要关注使用了何种网络插件,可以在插件或销毁容器时更加容易地配置网络。
Kubernetes 中有 Flannel、Calico、Weave 等主流的插件,在上一篇中,我们部署 Kubernetes 网络时,使用了 Weave,而在本章中,我们将使用 Calico 来部署网络。
对于 CNI ,后面的章节会深入学习。
Calico([https://github.com/projectcalico/calico](https://github.com/projectcalico/calico)) 是针对容器、虚拟机和裸机工作负载的开源网络和安全解决方案,它提供了 Pod 之间的网络连接和网络安全策略实施。
Flannel、Calico、Weave 都是常用的 Kubernetes 网络插件,读者可参考 [https://kubernetes.io/zh/docs/concepts/cluster-administration/networking/](https://kubernetes.io/zh/docs/concepts/cluster-administration/networking/) 这里不做过多的说明。
首先下载 Calico 的 yaml 文件。
```
wget https://docs.projectcalico.org/manifests/calico.yaml
```
然后我们需要留意 yaml 文件中的 `CALICO_IPV4POOL_CIDR` 的值,读者直接打开 [https://docs.projectcalico.org/manifests/calico.yaml](https://docs.projectcalico.org/manifests/calico.yaml) 或者使用 `less calico.yaml` 在终端上阅读文件。
找到 `CALICO_IPV4POOL_CIDR` 例如:
```bash
# - name: CALICO_IPV4POOL_CIDR
# value: "192.168.0.0/16"
```
这个表示 ip4 池,如果 ip 不存在,则会自动创建,创建 的 pod 的网络 ip 会在这个范围。默认是 `192.168.0.0` 我们不需要改,如果你需要定制,则可以删除 `#` ,然后改动 ip。
> **[Error] 提示**
>
> 请务必根据你集群中的 IP 段,配置此参数。
然后我们启用 Calico 网络插件:
```bash
kubectl apply -f calico.yaml
```
当网络配置完成后,即可使用 `kubeadm join` 加入节点。
## 其它
### 在节点上执行命令
如果我们在 Worker 节点上执行命令,会发现:
```bash
root@instance-2:~# kubectl describe nodes
The connection to the server localhost:8080 was refused - did you specify the right host or port?
```
首先在 Master 节点中,下载 `/etc/kubernetes/admin.conf` 文件,或者复制文件内容,到 Worker 节点中。
将文件上传或复制到 Worker 节点的 `/etc/kubernetes/admin.conf` 文件,执行配置即可。
```bash
mkdir -p $HOME/.kube
sudo cp -f /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```
```bash
export KUBECONFIG=/etc/kubernetes/admin.conf
```
```bash
echo 'export KUBECONFIG=/etc/kubernetes/admin.conf' >> $HOME/.bashrc
```
### 自动补全工具
`kubectl` 命令和可选参数非常多,每次都要敲长长的命令,容易出错,我们可以利用 `bash-completion` 为我们快速完成命令的输入。
```bash
sudo apt-get install bash-completion -y
```
```bash
source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> $HOME/.bashrc
```
当我们敲命令时,按下 TAB 键,会自动补全。
输入 `kubectl des` ,然后按一下 `TAB` 键,会发现内容自动补全为 `kubectl describe`。
### 状态描述
执行 `kubectl describe nodes` /命令,我们可以看到节点详细的信息,其中有个 `Conitions` 字段,描述了所有正在运行中(Running) 的节点的状态,它有 5 个字段或类型:
* Ready
Node 是否能够接收 pod ,如果可以则 `Status` 为 True;如果节点不健康,不能接收 pod,则 为 False。正常情况下为 True。
* DiskPressure
表示节点的空闲空间不足以用于添加新 Pod,如果为 True则说明不正常。
* MemoryPressure
表示节点存在内存压力,即节点内存可用量低,如果为 True 则说明不正常。
* PIDPressure
表示节点存在进程压力,即节点上进程过多;如果为 True 则说明不正常。
* NetworkUnavailable
表示节点网络配置不正确;如果为 True,则说明不正常。
如果使用 JSON 表示:
```javascript
"conditions": [
{
"type": "Ready",
"status": "True",
"reason": "KubeletReady",
"message": "kubelet is posting ready status",
"lastHeartbeatTime": "2019-06-05T18:38:35Z",
"lastTransitionTime": "2019-06-05T11:41:27Z"
}
]
```
读者可参考:[https://kubernetes.io/zh/docs/concepts/architecture/nodes/](https://kubernetes.io/zh/docs/concepts/architecture/nodes/)
本章内容主要介绍了 CKAD 认证中要求掌握的 kubeadm 部署 k8s 、配置启动 Calico 网络插件,跟上一篇的内容比较,主要是通过 yaml 文件去控制创建 kubernetes 集群,两章的部署过程一致,只是网络插件有所不同。
--------------------------------
标题:2.4 国内代理
来源:/zh/kubernetes/2.deploy/4.kubeadm_proxy
# 2.4 国内代理
在本章中,将会学习如果在国内服务器中拉取 Kubernetes 镜像,解决 kubeadm 网络问题。
首先按照 2.2 中的教程,安装好 `kubeadm`、`kubectl`、`kubelet` 三个工具。
## 配置
当使用 `kubeadm init` 命令时,如果不传递参数,则会使用默认的配置文件初始化集群,我们可以导出默认的配置文件内容。
```bash
kubeadm config print init-defaults > kubeadm.conf
```
找到大约在 31 行的 `imageRepository: k8s.gcr.io` ,修改为镜像地址:
```yaml
imageRepository: registry.aliyuncs.com/google_containers
```
通过配置文件kubeadm 会在阿里云源拉取镜像。
```bash
kubeadm config images pull --config kubeadm.conf
```
但并不是所有 Kubernetes 镜像阿里云源都有,可能有些镜像会报错。


例如 coredns 镜像,阿里云源的 `google_containers` 仓库中不能直接拉取到,那么我们可以手动拉取再改 tag。
```
docker pull coredns/coredns:latest
```
```
docker tag coredns/coredns:latest registry.aliyuncs.com/google_containers/coredns/coredns:v1.8.0
```
## 使用
当我们要在国内的服务器创建集群时,在 `kubeadm init` 命令后面指定代理源。
```
kubeadm init --image-repository registry.aliyuncs.com/google_containers
```
耐心等待,即可完成集群的初始化。
--------------------------------
标题:2.5 Dashboard
来源:/zh/kubernetes/2.deploy/5.dashboard
# 2.5 Dashboard
Kubernetes-Dashboard 是一个 管理 Kubernetes 集群的 Web UI,其界面大气优雅,跟 kubectl 一样,其后端是 API-Server,通过它可以查看集群中每个集群的节点状态,各类资源的调度状况,还可以动态伸缩节点、管理资源。
其界面如下:

dashboard 的 Github 仓库地址为:https://github.com/kubernetes/dashboard/tags,请从中选择最新版本的 yaml。
使用在线的 YAML 文件部署 Kubernetes-Dashboard :
```bash
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.2.0/aio/deploy/recommended.yaml
```
> 请替换 url 中的 2.2.0。
dashboard 创建后会出现在 kubernetes-dashboard 命名空间中。
```bash
root@instance-1:~# kubectl get pods --namespace=kubernetes-dashboard
NAME READY STATUS RESTARTS AGE
dashboard-metrics-scraper-856586f554-4nd9v 1/1 Running 0 9d
kubernetes-dashboard-78c79f97b4-288js 1/1 Running 0 9d
root@instance-1:~# kubectl get services --namespace=kubernetes-dashboard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dashboard-metrics-scraper ClusterIP 10.98.50.123 8000/TCP 9d
kubernetes-dashboard NodePort 10.111.44.44 443/TCP 9d
```
> **[Error] 提示**
>
> 如果你的集群上有多个节点,那么可能会被放到某个节点上。
接着,查看 dashboard 被放到哪个节点上运行:
```bash
root@master:~# kubectl get pods --namespace=kubernetes-dashboard -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
dashboard-metrics-scraper-c45b7869d-d9s2m 1/1 Running 0 15m 10.32.0.5 slave1
kubernetes-dashboard-576cb95f94-8dzcj 1/1 Running 0 15m 10.32.0.4 slave1
```
可以看到,笔者的 dashboard 服务被放到 slave1 节点上去了,接着请记录 slave1 的服务器 ip。
由于 dashboard 网络默认是 ClusterIP 方式,因此外网是不能访问的,所以为了能够被外界访问,可以修改其 service。
```bash
kubectl edit service kubernetes-dashboard --namespace=kubernetes-dashboard
```
将 ` type: ClusterIP ` 改成 ` type: NodePort`,然后直接保存即可(会使用 vi 编辑器打开文件,修改和保存请使用 vi 方式处理)。
```yaml
ports:
port: 443
protocol: TCP
targetPort: 8443
selector:
k8s-app: kubernetes-dashboard
sessionAffinity: None
type: NodePort
```
然后执行 `kubectl get services --namespace=kubernetes-dashboard -o wide` 命令,查看随机生成的端口。
```bash
root@master:~# kubectl get services --namespace=kubernetes-dashboard -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
dashboard-metrics-scraper ClusterIP 10.97.89.174 8000/TCP 40m k8s-app=dashboard-metrics-scraper
kubernetes-dashboard NodePort 10.108.209.60 443:31984/TCP 40m k8s-app=kubernetes-dashboard
```
笔者的端口是 31984,接着可以在外网访问 `https://{ip}:31984`。注意,访问的 IP 是部署了 dashboard 的节点的 I。
打开网页后,其页面如下:

可以看到,访问方式有 Token 和配置文件方式(kubeconfing),这两者后面再讲。
通过下面这条命令我们可以查看 Token:
```bash
kubectl -n kube-system describe $(kubectl -n kube-system get secret -n kube-system -o name | grep namespace) | grep token
```
复制 token,填写到 Web UI 中,即可进入控制台。
--------------------------------
标题:导读
来源:/zh/kubernetes/3.pod/README
# 第三章:Pod部署和调度
## 学习目标
**部署、配置、标签选择、调度、副本、控制器、有状态的无状态的 Pod**
* 讨论部署配置细节
* 通过 `kubectl create/apply` 创建 Deployment ,部署 Pod;
* 通过 `kubectl edit` 修改 对象;
* 如何查看对象信息,`kubectl get`、`kubectl describe`、`-o wide`、`-o yaml`;
* 创建 Service,`kubectl expose`;
* 设置副本集,`replicaset`;
* 查看对象支持的属性 `kubectl explain pods`,`kubectl explain pod.spec`
* 向上和向下扩展部署。
* 扩容 Pod,设置副本集,`kubectl scale`、`kubectl edit`;
* 自动扩容 Pod,水平缩放,比例缩放,根据 CPU、内存缩放,`kubectl autoscale`;
* DaemonSet
* 实现滚动更新和回滚
* 更换镜像版本、更新 Pod,`kubectl set image`;
* 设置滚动更新速度,`maxSurge`、`maxUnavailable`;
* 暂停和恢复更新,`kubectl rollout pause` 、`kubectl rollout resume`;
* 回滚旧版本,`kubectl rollout undo`;
* 使用标签选择各种对象以及调度
* 了解 label、selector、nodeSelector;
* 查询 label、选择 label、使用选择器调度 Pod;
* 选择器运算符,等值选择、集合选择;
* 配置污点和容忍度
* 亲和性和反亲和性
* 配置污点和容忍度
* 系统默认污点、master 的调度配置
* Job 和 CronJob
* 了解 Job,完成数(conpetions)、工作队列(parallelism)、控制并行性、清理 Job。
* CronJob 的时间表示
* StatefulSet
* 有状态的应用
掌握以下命令的使用:
kubectl 原理是请求 apiserver 完成某些操作,日常操作中,最常用的就是 kubectl。
`kubectl create {对象}` ,创建 deployment、job 等对象。
`kubectl apply -f` 应用 yaml 文件,完成某些操作。
`kubectl get {对象}` 查询对象。
`kubectl scale {对象}` 伸缩对象数量(ReplicaSet)。
`kubectl expose` 创建 Service。
`kubectl describe` 获取对象详细的信息。
`kubectl exec` 在对象中执行命令,例如 pod。
--------------------------------
标题:3.1 Pod
来源:/zh/kubernetes/3.pod/1.pod
# 3.1 Pod
Pod 在 Kubernetes 中是最重要的对象之一,Pod 集群中**创建和管理的、最小的可部署**的计算单元。
为什么这样说呢?
因为在 Kubernetes 中部署一个应用时,创建的便是 Pod,一个 Pod 中包含一个或多个容器,这些容器在 Pod 中能够共享网络、存储等环境,然后组成一个应用对外提供服务。
我们不必过于关注这些概念,我们只需要知道,在 Kubernetes 中,是不会直接操作容器的,而是通过 Pod 封装了容器,集群通过管控 Pod ,便可控制容器的存储、网络等资源,实现资源隔离或共享。
学习 Kubernetes,Pod 是**最重要最基本**的知识,本章将介绍什么是 Pod、Pod 的结构等,如何使用 Pod 部署应用,读者需要多加练习。
## Pod 基础知识
### 创建 Pod
nginx 是工作中最常用的中间件之一,在本小节中,我们将尝试在 Kubernetes 中通过 Pod 创建 nginx。
创建 一个 Pod 或者说通过 Pod 部署应用很简单,示例命令如下:
```bash
kubectl run nginxtest --image=nginx:latest --port=80
```

`nginxtest` 为 Pod 名称,并且为其映射端口为 80。
如果我们使用 docker 部署一个 nginx,那么命令可以这样写:
```bash
docker run --name nginxtest -p 80:80 nginx:latest
```
在这个例子中,我们创建了一个 nginx 应用,并且为其暴露了 80 端口。
> **[Info] 提示**
>
> 一般部署应用使用的是 Deployment 、Daemon 等,而不会直接部署 Pod;Deployment、Daemont 可以监控 Pod,当 Pod 故障时,会对其进行重新部署等操作。相比之下,手动创建的 Pod 不被 “托管” 起来,如果 Pod 故障或者其他原因消失了,是不会自动恢复的。
Kubernetes Pod 中的所有容器共享一个相同的 Linux 命名空间(network、UTS、IPC),而不是每个容器一个命名空间,这一点与 Docker 不同,因此 Pod 中的多个容器使用相同的网络、主机名称,看到的是同一个 IP 地址,不过进程还是按照容器进行隔离。
> 据说在新版本的 Kubernetes 和 Docker 中, PID 命名空间也可以设置为相同的。
由于 Mount、User 命名空间不共享,因此在容器中,**文件系统和用户是隔离的**。
在 Kubernetes 中,每个 Pod 的网络都是独立的,每个 Pod 都有自己的 IP 地址,在我们没有使用 Service 为 Pod 之前,Pod 只能通过专属的 IP 地址访问。
> 会在后面的章节中介绍 Service。
我们现在来查看 Pod 的运行状态以及一些简单的信息:
```bash
kubectl get pods -o wide
```

然后通过图中显示的 IP 来访问 nginx 服务:

后面我们还有很多实验要做,这里让我们先删除这个 Pod。
删除命令如下:
```bash
kubectl delete pod nginxtest
```
我们创建 Pod 的命令很简单,但是 Pod 能做的不止这一点,如果需要发挥 Pod 的全部能力,就需要使用 YAML 文件了。
在 Kubernetes 中,可以通过 YAML 文件定义资源,然后通过 YAML 文件中的描述信息,告诉 Kubernetes 怎么创建 Pod 、管理 Pod。
接下来,我们通过一个 YAML 文件来部署 Pod。
使用 YAML 文件定义需要的 Pod 格式示例如下:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: nginxtest
spec:
containers:
- image: nginx:latest
name: nginxtest
ports:
- containerPort: 80
protocol: TCP
```
> 取名为 nginx.yaml。
然后,我们将这个 YAML 应用到 Kubernetes 中:
```bash
kubectl apply -f nginx.yaml
```
### 了解 Pod
Pod 是 Kubernetes 中调度资源的最小单位,一个 Pod 中可以包含多个容器,Pod 中的容器被打包在一起作为一个整体, **Pod 中的容器不会被分配到不同节点中**,**它们一定被部署到同一个节点中**。
每个 Pod 有且只有一个唯一的 IP 地址,通过 `kubectl get pod {pod名称} -o wide` 可以查询到。
```bash
root@master:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginxtest 1/1 Running 0 12m 10.32.0.2 slave1
```
> **[Info] 提示**
>
> `-o wide` 可以查看对象更多的信息。
每个 Pod 之间都是隔离的,也就是在 Pod 内无论怎么使用端口,都不会对另一个 Pod 产生影响。
但是在 Pod 内,因为**各个容器都是共享同一个 Pod 的网络,因此一个 Pod 内,不允许有两个进程占用相同的端口**。
**Pod IP 可以 Ping 通**,**Pod 之间可以通过 Pod IP 访问。**

我们可以把一个 Pod 形容为一个虚拟主机。
在 Pod 中,所有容器(进程)都在一个主机上,我们知道,同一个主机上的所有进程共享着主机网络,多个进程自然不能同时占用一个端口,否则会冲突。
容器共享 Pod 中的网络,所以 Pod 中的容器也能够通过 localhost 相互访问,因此 Pod 中的 容器(进程)不能暴露/使用相同的端口。

通过 Pod IP 和 `locahost` ,解决了 高度耦合的间通信问题。既实现了不同应用之间的网络隔离,也满足了应用内的通讯需求。
> 在 Kubernetes 中,一个应用可以只有一个进程,也可以是一个多容器组成的 Pod,也可以是多个 Pod 组成的。
其实通过 Docker ,也可以做到类似的效果。
Docker 有一个 Contaner 模式,能够让多个容器共享一个 Network Namespace,可以让一个容器和另一个容器共享 IP、端口范围。
假如容器 A 已经创建起来,那么容器 A 会创建一个虚拟网卡;然后指定 容器 B 与容器 A 共享网络,那么两者就可以直接通过 localhost 进行通讯。

前面提到,Pod 中的容器共享了同一个网络,但是进程是隔离的,也就是 Pod 中的容器是部分隔离的。
除了进程是隔离的之外,每个容器都有自己的文件系统,各自的文件都会被隔离,一个容器不能访问或修改其它容器的文件。
为了让多个 容器之间能够共享文件,可以使用卷,把同一个卷映射到容器中,当然这是后话,我们在第五章的时候才会学到。

### Pod 是怎样启动的
在 Kubernetes 中,当创建 Pod 时,会先启动一个 pause 容器,然后 Pod 中我们定义的容器会以 Container 模式共享 pause 中的网络。
> 即是是一个小节中的 Docker Contaner 模式。
使用下面的命令查看节点中的所有容器:
```
docker ps | grep pause
```

其中有一条:
```
k8s_POD_nginxtest_default_dd8879d5-23b0-43db-b1e5-71ecb79268d2_0
```

这个 pause 容器创建了网络,然后 nginxtest 容器以 Container 模式连接到了这个 pause 容器的网络,这时就能通过 IP 访问 nginxtest 了。
> **[Info] 提示**
>
> 当然,pause 不只是实现容器的网络互通,还有其它功能。
在第一章的 Docker 介绍中,谈到过此类容器,其目的是让其他容器联通起来, pause 在 Pod 的网络中相当于交换机。
### Pod 生命周期
当 Pod 被分配到某个节点时, Pod 会一直在该节点运行,直到停止或被终止,Pod 在整个生命周期中只会被调度一次。也就是说 Pod 是一次性的,如果 Pod 故障了,那么如果需要恢复应用的运行,是需要重新创建 Pod 的,而不是重启 Pod。
Pod 的整个生命周期可能有四种状态:
* Pending,尝试启动容器,如果容器正常启动,则进入下一个阶段;
* Running,处于运行状态;
* Succeeded、Failed,正常结束或故障等导致容器结束;
* Unknown,因为某些原因无法取得 Pod 的状态。
为了观察 Pod 的生命周期中的事件,我们可以创建一个 Deployment,查看当前正在发生的事件:
> 这里只需要了解 Pod 的生命周期,关于 Deployment,我们后面才会学习到。
```bash
kubectl create deployment nginx --image=nginx:latest --replicas=3
```
查看当前命名空间(default)中发生的事件:
```bash
kubectl get events
```

通过 `kubectl describe pod {pod名称}` 查看一个 Pod 的详细信息,里面有这个 Pod 的事件:
```
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m22s default-scheduler Successfully assigned default/nginx-* to instance-2
Normal Pulling 3m19s kubelet Pulling image "nginx:latest"
Normal Pulled 3m17s kubelet Successfully pulled image "nginx:latest" in 2.570763819s
Normal Created 3m17s kubelet Created container nginx
Normal Started 3m16s kubelet Started container nginx
```
上面查询到的事件均发生在 Pod 的 `Pending` 状态,我们可以看到在这个阶段中,Pod 被调度,然后拉取镜像、启动容器,如果容器启动成功,Pod 便会进入 `Running` 状态。
> 事件记录保存在 etcd 中。
在 Kubernetes 中,**Pod 被认为是相对的临时性实体,而不是长期存在的**。由于 Pod 本身不具有治愈能力,如果 节点故障或者节点资源耗尽、节点被维护、Pod 被驱逐等,那么 Pod 无法在节点上继续存活。无论 Pod 因为何种原因被删除,在 Pod 中的网络、存储卷等,也会被销毁,新的 Pod 被创建时,相关的网络、存储卷也会被重建。
【图来源:[https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/](https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/)】
> **[Info] 提示**
>
> 由于 Pod 是临时性的,为了保障服务能够在 Pod 挂了后自动重建,可以使用 Deployement、Daemon 等对象管理 Pod,这些对象被称为 控制器。
>
> 为了 Pod 创建创建后可以恢复服务,可以挂载卷以便持久化存储数据、创建 Service 以便对外固定 IP 访问。
>
> **不过 Pod 一般应该是无状态的**。
在删除 Pod 时,Kubernetes 会终止 Pod 中的所有容器,会向容器中的进程发生 SIGTERM 信号,等待进程的正常关闭,所以 Pod 可能不会被马上删除,当然如果进程不能正常关闭,Kubernetes 最多等待 30s,然后会使用 SIGKILL 强制杀死进程。
### 容器重启策略
这是一个简单的 Pod 的 YAML 定义:
```yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:latest
imagePullPolicy: Always
name: nginx
... ...
restartPolicy: Always
```
在这个 YAML 中,有两个`*Policy`,取值有 Always、OnFailure 和 Never 三种,它们代表了某种生命周期策略。
对于每个容器,都可以设置 `imagePullPolicy` ,指示在拉取镜像时如果失败,是否进行重试。
在 `spec` 中,有个 `restartPolicy` 字段,其值默认为 `Always`,指示 Pod 中所有容器的重启动作,但是在 `Deployment`、`StatefulSet`、`DaemonSet` 等控制器中,`restartPolicy` 只支持 Always ,不支持 OnFailure 和 Never 。
> **[Info] 提示**
>
> 如果容器启动失败,重试间隔会越来越长。 kubelet 会在 10s 后重试第一次,如果还是失败,第二次 20s 后再重试;按照 10s、20s、40s、80s ... 的间隔重试,但最长不超过 5 分钟。如果容器被成功运行且运行了 10 分钟以上,那么计时器会被重置,下次出现故障时,按照 10s、20s 的间隔时间重试。
如果单独创建 Pod,并且设置了 `restartPolicy: Always`,那么 Pod 会一直停留在此节点上,如果 容器故障,Pod 可能会无限重试。
如果我们使用 `Deployment`、`StatefulSet`、`DaemonSet` 等控制器管理 Pod,这些控制器能够处理 Pod/副本 的管理、上线,并且在 Pod 失效时提供治愈能力,当,当然,这些东西后面再提。
节点上的 Pod 停止工作时,可以创建替代性的 Pod, Pod 被调度到一个健康的节点执行。
> **[Info] 提示**
>
> 为什么要使用控制器管理 Pod 呢?
>
> 举个例子,笔者有个朋友,写了个 A 程序,这个程序能够在执行后,关闭计算机(关机)。本来是需要的时候执行一次,但是后面配置了一个守护进程,这个守护进程会自动启动 A 程序。这样出现了无限循环,开机 -> 启动守护进程 -> A 出现 -> 关机,结果这个朋友的电脑无法正常开机,一开机就被关机。
>
> 这种情况跟单独的 Pod 部署类似,很容易因为某些原因无限重试,并且一直驻留在节点上。因为重试是在原来的基础上进行重试,使用原来的文件、数据、网络等。控制器则可以将其重置,恢复 `出厂设置`。
>
> 如果一个节点上的 CPU、内存不够用了,那么容器有可能因为资源不足,无法启动,导致无限重试;如果使用控制器,控制器会在有充足资源的、健康的节点上重建 Pod。
## Pod 的部署和管理
但是一般很少直接创建或管理 Pod,一般使用控制器来管理 Pod。下面列出一些控制器,我们来提前了解一下,在后面的学习中我们会一步步深入学习这些控制器。
* Deployment
* StatefulSet
* DaemonSet
> **[Error] 注意**
>
> 单独创建 Pod ,一般用于临时调试、学习等目的,生产中一般不会这样玩。
### 创建 Pod
在 Kubernetes 中,所有对象都可以使用 YAML 表示。一般很少用命令参数来创建对象,因为命令参数没有那么灵活和那么多的选项可以用。
一般都是用 YAML 文件来描述怎么创建 Pod ,为 Pod 分配多少资源等。
下面我们来看看创建一个 Pod 的基本模板:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:latest
```
如果要映射网络端口,则 YAML 文件模板为:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
protocol: TCP
```
> **[INFO] 提示**
>
> 由于 Pod 中的容器共享相同的网络,并且对外提供了 IP,因此不加上 `ports`,通过 IP 和端口照样能够访问 Pod 中的应用。
>
> 在 YAML 里面加上容器端口,主要有助于其他人快速了解此 Pod 的定义信息。
将上面的 YAML 内容复制到 pod.yaml 中,然后执行命令应用 YAML :
```bash
kubectl apply -f pod.YAML
# 或
kubectl create -f pod.YAML
```
> 使用 `kubectl run` 命令用于参数式的命令:
>
> ```bash
> kubectl run nginx-pod --image=nginx:latest
> ```
>
> `kubectl create` 用于创建资源;
>
> `kubectl apply` 可以创建资源,如果资源已经存在,则更新资源;
### 覆盖容器命令
在 Pod 中可以配置容器的一些信息,也可以替换容器的启动命令,其配置格式如下:
```yaml
spec:
containers:
- name: nginx
image: nginx:latest
command: ["/bin/command"]
args: ["arg1","arg2","arg3"]
```
### Pod 管理
输入 `kubectl get pods` 可以查看名为 default 命名空间的 Pod;
输入 `kubectl get pods -o wide` 可以查看多几个字段的 Pod 信息;
输入 `kubectl describe pods` 可以查看每个 Pod 的所有详细信息。
```bash
root@instance-2:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 11s 192.168.56.3 instance-2
```
可以看到 Pod 在第二个节点上部署,其 IP 为 `192.168.56.3`。
在没有按照网络插件的情况下,Pod 的 IP 只能在被部署服务的节点上访问,不同节点不能访问其的 Pod。
> 这个会在第四章的 Service 部分说明。
nginx 默认使用了 80 端口,因此通过 `192.168.56.3:80` ,可以直接访问到容器中的 nginx 服务。

> **[Error] 注意**
>
> 只能在 Pod 部署的节点上连接到此 IP。如果要在不同的节点访问 Pod,需要安装 CNI 网络插件,如 flannel、calico、weave 等,在 2.2、2.3 中有介绍。
### 查看日志
在 Docker 中,我们可以通过 `docker logs {容器id}` 来查看容器中的日志,这些日志是进程打印到控制器的标准输出,例如 C# 的 `Console.Write`、C 语言的 `printf`、Go 语言的 `fmt.Print`,Docker 的 本地日志驱动会捕获容器的 stdout/stderr 输出记录驱动器。
Docker 日志默认限制日志大小为 10 MB ,每天会轮替一个日志文件,并使用自动压缩来减少磁盘文件大小。
> Docker 日志驱动程序使用基于文件的存储。文件格式和存储机制被设计为由 Docker 守护进程独占访问,不应被外部工具使用,因为在未来的版本中实现可能会发生变化。
>
> 笔者没有明确查找到 Docker 的日志具体限制情况,以上内容来自 [https://docs.docker.com/config/containers/logging/local/](https://docs.docker.com/config/containers/logging/local/) 的参考资料。
在 Kubernetes 中,也可以通过类似的命令快速查看 Pod 中的容器的日志。
如果 Pod 中只有一个容器,则直接使用类似命令即可:
```
kubectl logs {pod名称}
```
如果 Pod 中有多个容器,则需要指定容器名称:
```
kubectl logs {pod名称} -c {容器名称}
```
> `kubectl logs -f` 可以一直实时看到容器打印的日志。
**`kubectl logs` 只能获取当前正在运行的 Pod 的日志,如果 Pod 被删除,则所有日志记录都会被删除**。
> 查看、维护 Pod 状态,比较常用的命令有:
>
> * **kubectl get** - 列出对象资源,如 `kubectl get pods`;
> * **kubectl describe** - 显示有关资源的详细信息,如 `kubectl describe pod nginxtest`;
> * **kubectl logs** - 打印 pod 和其中容器的日志;
> * **kubectl exec** - 在 pod 中的容器上执行命令,格式为 `kubectl exec {pod名称} -c {容器名称} -- {要执行的命令}`, `--` 用于分隔命令,其后面的参数均表示要传递进容器的命令。
--------------------------------
标题:3.2 Deployment 部署
来源:/zh/kubernetes/3.pod/2.deployment
# 3.2 Deployment 部署
Deployment 是 Kubernetes **提供的一种自我修复机制来解决机器故障维护的问题**。
前面提到了单独部署 Pod,但是这种方式只适合临时的 Pod,用于测试调试。
如果要用于生产,则需要 Deployment 等控制器**管理部署 Pod,维持 Pod 的副本数量以及 Pod 监控和维护**。
对于 Kubernetes 对象的部署,例如 Pod、Deployment、Service 等,有三种部署方式:
* Using Generators (Run, Expose)
* Using Imperative way (Create)
* Using Declarative way (Apply)
在 3.1 章中,我们已经学习了 `Run` 和 `apply` 等,在本篇以及后面的章节中,我们会一步步深入学习这些部署方式。
本篇包含或需要掌握以下内容:
> * 创建 Deployment
> * 修改 Deployment
> * 查看 Deployment 、Pod、Services、副本
在 3.1 章中,我们了解如何使用 Pod 创建一个 Nginx ,也学习了 Pod 的一些概念、网络等知识,当时笔者提到了 Deployment,那么在本章中,我们将会来学习如何使用 Deployment 来部署应用,并学会管理 Pod。
## Deployment
当我们单独使用 docker 部署应用时,为了让应用挂了后能够重启,我们可以使用 `--restart=always` 参数,例如:
```bash
docker run -itd --restart=always -p 666:80 nginx:latest
```
但是这种方式只能单纯重启容器,并**不具备从机器故障中恢复的能力**,即**当一台服务器挂了后,此服务器上所有的容器全部挂掉**。
Kubernetes Deployment 是一种 Pod 管理方式,它可以指挥 Kubernetes 如何创建和更新你部署的应用实例,创建 Deployment 后,Kubernetes 会将应用程序调度到集群中的各个节点上。
Kubernetes Deployment 提供了一种与众不同的应用程序管理方法。
Deployment 的创建,有两种方法,一种是直接使用命令创建(`kubectl create`),一种是通过 YAML(`kubectl apply`),后面我们会介绍这两种创建方法。
### 创建 Deployment
在 Kubernetes 中,Pod 是调度的最小单位,一个 Pod 中包含多个 容器,所以我们的各种操作都是在 Pod 之上。
我们来使用 deployment 部署一个 Pod,这个 Pod 包含一个 Nginx 容器。
```bash
kubectl create deployment nginx --image=nginx:latest
```
格式:
```bash
kubectl create deployment {deployment对象名称} --images={镜像名称和标签}
```
此时,nginx 容器会以 Pod 的方式部署到节点中,但是被部署到哪个节点是随机的,如果你只有一个 worker 节点,则 Pod 必定在这个 Worker 节点上。当然,我们可以获取到具体的调度信息,从中查看 Pod 被调度到哪个节点。
```bash
root@instance-1:~# kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx 1/1 1 1 52s nginx nginx:latest app=nginx
root@instance-1:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-55649fd747-s4824 1/1 Running 0 61s 192.168.56.4 instance-2
```
可以看到, Pod 在 instance-2 中运行着。
Deployment 会为我们自动创建 Pod,Pod 由 `{deployment名称}-{随机名称}` 组成。
> **[Info] 提示**
>
> 还有一个地方也说一下,`kubectl get xxx` 时,带不带 `s` 都没关系,例如 `kubectl get nodes` / `kubectl get node` 都是一样的。
>
> 不过,一般从语义上,我们获取全部对象时,可以使用 `kubectl get nodes`,获取具体的对象时,可以使用 `kubectl get node nginx`。类似的,`kubectl describe nodes` 、`kubectl describe node nginx`。实际上加不加 `s` 都一样。
### kubectl apply/create
当我们创建一个 deployment 时,`kubectl create` 和 `kubectl apply` 效果是一样的,但是 `apply` 还具有更新(update) 的功能。
`kubectl apply` 会在以前的配置、提供的输入和资源的当前配置之间 找出三方差异,以确定如何修改资源,`kubectl apply` 命令将会把推送的版本与以前的版本进行比较,并应用你所做的更改, **但是不会自动覆盖任何你没有指定更改的属性**。
另外还有 `kubectl replace` 、`kubectl edit`。
`kubectl replace` 是破坏性更新/替换,容易导致问题;
`kubectl edit` 可以更新 Deployment 等已存在的对象。
根据 Kubernetes 官方的文档说明,**应始终使用 `kubectl apply` 或 `kubectl create --save-config` 创建资源**。
前面已经学习了 `kubectl create`,这里学习一下 `kubectl apply`。
通过 YAML 文件部署 nginx:
```bash
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
```
> 很多开源软件提供了 YAML 文件,我们通过 YAML 文件可以快速部署服务,如 Redis、Consul 等。
>
> 开发者可以将自己的软件或者中间件等,制作为 yaml 文件,这样当别人执行 yaml 文件时,会很容易地将一个软件安装到别的 kubernetes 环境中。
这里再说一下创建 Deployment 的区别。
如果使用 create 创建,命令格式:
```bash
kubectl create deployment {deployment的名字} --image={镜像名称}
```
如果使用 apply 命令创建,YAML 中需要指定一些信息,可定制性很高。
```yaml
kind: Deployment
... ...
medatada:
name:nginx
... ...
spec:
containers:
- image: nginx:latest
```
然后执行 `kubectl apply -f xxx.yaml` 文件。
一个是 `kubectl create deployment` ;
另一个是 `kubectl apply -f`,在 yaml 中指定 `kind: Deployment`。
如果我们只需要快速创建,使用命令形式就行;如何生产生产,还是得使用 YAML 文件,并于留存记录。
要删除一个对象,可以使用 `kubectl delete -f {名称}.yaml`,如删除 calico。
```bash
kubectl delete -f calico.yaml
```
### 检查 YAML
有时我们不知道我们的创建命令或 yaml 是否正确,可以使用 `--dry-run=client` ,`--dry-run=client` 参数来表示当前内容只是预览而不真正提交。
```bash
kubectl create deployment testnginx --image=nginx:latest --dry-run=client
```
在一些 k8s 认证中,我们没时间一点点写 yaml ,但是又需要定制,此时可以使用 `--dry-run=client -o yaml` ,既可以不生效 Deployment,又可以导出 yaml 文件。
> **[Info] 提示**
>
> `-o wide` 可以查看对象更多的字段信息;`kubectl describe` 可以查看对象的全部详细信息;`-o yaml` 或 `-o json` 可以查看对象的定义/描述文件。
>
> `--dry-run` 取值必须为none、server或client。如果客户端策略,只打印将要发送的对象,而不发送它。如果是服务器策略,提交服务器端请求而不持久化资源。
命令示例如下:
```bash
kubectl create deployment testnginx --image=nginx:latest --dry-run=client -o yaml
# -o json 可以输出 json 格式
```

使用这样的方法,可以快速获得需要的 YAML 模板,然后复制到 YAML 文件,根据需要改动、定制。除了 Deployment,其它 kubernetes 对象也可以使用这种方法。
### 查看 Deployment
我们以 Deployment 的方式部署 Pod ,就会创建一个 Deployment 对象,获得 deployment 列表:
```bash
kubectl get deployments
kubectl get deployments -o wide
```
```bash
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 2m24s
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx 1/1 1 1 2m42s nginx nginx:latest app=nginx
```
> 在 `kubectl get ...` 后面加上 `-o wide` 可以获得更多的标签信息。
使用 `kubectl get events` 可以获得集群中最近发生的事件,如创建 Deployment 到部署容器过程的详细事件记录。
```
Successfully assigned default/nginx-55649fd747-wdrjj to instance-2
Pulling image "nginx:latest"
Successfully pulled image "nginx:latest" in 8.917597859s
Created container nginx
Started container nginx
Created pod: nginx-55649fd747-wdrjj
Scaled up replica set nginx-55649fd747 to 1
```
使用 `kubectl describe deployment nginx` 可以获得更加详细的信息,是各种信息的集合。

### 查看 Pod
我们通过 Deployment 创建了应用,当时这些 Pod 是怎么跟 Deployment 对象关联起来的?接下来我们需要了解如何查看 Pod 。
```bash
kubectl get pods
```
```bash
NAME READY STATUS RESTARTS AGE
nginx-55649fd747-msw8g 1/1 Running 0 4h16m
```
可以看到一个 Pod 名为 `nginx-` ,因为我们是利用 Deployment 部署 Pod 的,没有指定这个 Pod 的名称,所以默认 Pod 名称以 Deployment 名称为前缀。
我们查看这个 pods 被部署到了哪个节点上:
```bash
kubectl get pods -o wide
```
```bash
NAME READY STATUS RESTARTS AGE IP NODE
nginx-55649fd747-msw8g 1/1 Running 0 4h19m 192.168.56.57 instance-2
```
可以看到,这个 Pod 在 `instances-2` 这个节点上,同时这个 Pod 也有一个 IP,Kubernetes 会为每个 Pod 分配一个唯一的 IP,这个 IP 可以在节点上访问,其它 Pod 也可以通过 IP 访问此 Pod。
由于这个 Pod 里面的容器是 Nginx(80端口),所以我们可以访问这个 IP 可以打开 Nginx 页面。
```
root@instance-1:~# curl 192.168.56.57
Welcome to nginx!