Contents

云原生-k8s-教程

k8s官网

k8s官网文档

k8s官网教程

k8s easydoc

github yeasy/docker_practice及其最新版本电子书

注意事项

minikube使用kubctl时要在kubctl前面添加minikube,所以可以在.bashrc里面添加

1
alias kubectl="minikube kubectl --"

另外需要在.bashrc里面添加no_proxy环境变量,具体设置见官网

curl和谷歌浏览器并不会识别.bashrc里面的环境变量,所以你需要在curl后面添加--noproxy "*"来访问k8s的服务service

概念

概述

Kubernetes 组件

Kubernetes 组件

教程

你好,Minikube

安装好minikube后使用该命令启动minikube服务

1
minikube start

创建 Minikube 集群

使用 URL 打开仪表板

1
minikube dashboard --url

node-pod-container

创建管理 Pod 的 Deployment(在控制平面的结构,和Pod对应)

1
kubectl create deployment hello-node --image=registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port=8080

查看 Deployment

1
kubectl get deployments

查看 Pod

1
kubectl get pods

查看集群事件

1
kubectl get events

查看 kubectl 配置

1
kubectl config view

创建 Service

将 Pod 暴露为 Kubernetes Service,使得外部网络可以访问里面的容器

kubectl expose 命令将 Pod 暴露给公网

1
kubectl expose deployment hello-node --type=LoadBalancer --port=8080

查看创建的 Service

1
kubectl get services

支持负载均衡器的云服务平台将提供一个外部 IP 来访问该服务。 在 Minikube 上,LoadBalancer 使得服务可以通过命令 minikube service 访问。

访问服务

1
minikube service hello-node

启用插件

列出当前支持的插件

1
minikube addons list

启用插件

1
minikube addons enable metrics-server

查看创建的 Pod 和 Service

1
kubectl get pod,svc -n kube-system

禁用 metrics-server

1
minikube addons disable metrics-server

清理

1
2
3
4
5
6
kubectl delete service hello-node
kubectl delete deployment hello-node

minikube stop

minikube delete

Kubernetes 基础

创建集群

Kubernetes 协调一个高可用计算机集群,每个计算机作为独立单元互相连接工作

Kubernetes 以更高效的方式跨集群自动分发和调度应用容器

集群图

Master 负责管理整个集群

Node 是一个虚拟机或者物理机,它在 Kubernetes 集群中充当工作机器的角色。Node 使用 Master 暴露的 Kubernetes API 与 Master 通信。

使用 kubectl 创建 Deployment

Deployment 指挥 Kubernetes 如何创建和更新应用程序的实例。创建 Deployment 后,Kubernetes master 将应用程序实例调度到集群中的各个节点上。

Kubernetes Deployment 控制器会持续监视这些实例。 如果托管实例的节点关闭或被删除,则 Deployment 控制器会将该实例替换为集群中另一个节点上的实例。 这提供了一种自我修复机制来解决机器故障维护问题。

查看 pod 和工作节点

Pod 是 Kubernetes 抽象出来的,表示一组一个或多个应用程序容器(如 Docker),以及这些容器的一些共享资源。这些资源包括:

  • 共享存储,当作卷
  • 网络,作为唯一的集群 IP 地址
  • 有关每个容器如何运行的信息,例如容器镜像版本或要使用的特定端口

Pod是 Kubernetes 平台上的原子单元。当我们在 Kubernetes 上创建 Deployment 时,该 Deployment 会在其中创建包含容器的 Pod (而不是直接创建容器)。每个 Pod 都与调度它的工作节点绑定,并保持在那里直到终止(根据重启策略)或删除。 如果工作节点发生故障,则会在集群中的其他可用工作节点上调度相同的 Pod。

一个 pod 总是运行在 工作节点。工作节点是 Kubernetes 中的参与计算的机器,可以是虚拟机或物理计算机,具体取决于集群。每个工作节点由主节点管理。工作节点可以有多个 pod ,Kubernetes 主节点会自动处理在集群中的工作节点上调度 pod 。 主节点的自动调度考量了每个工作节点上的可用资源。

每个 Kubernetes 工作节点至少运行:

  • Kubelet,负责 Kubernetes 主节点和工作节点之间通信的过程; 它管理 Pod 和机器上运行的容器。
  • 容器运行时(如 Docker)负责从仓库中提取容器镜像,解压缩容器以及运行应用程序。

使用 kubectl 进行故障排除

  • kubectl get - 列出资源
  • kubectl describe - 显示有关资源的详细信息
  • kubectl logs - 打印 pod 和其中容器的日志
  • kubectl exec - 在 pod 中的容器上执行命令

公开地暴露你的应用

Pod 实际上拥有 生命周期。 当一个工作 Node 挂掉后, 在 Node 上运行的 Pod 也会消亡。 ReplicaSet 会自动地通过创建新的 Pod 驱动集群回到目标状态,以保证应用程序正常运行。

Kubernetes 中的服务(Service)是一种抽象概念,它定义了 Pod 的逻辑集和访问 Pod 的协议。

尽管每个 Pod 都有一个唯一的 IP 地址,但是如果没有 Service ,这些 IP 不会暴露在集群外部。

Service 也可以用在 ServiceSpec 标记type的方式暴露:

  • ClusterIP (默认) - 在集群的内部 IP 上公开 Service 。这种类型使得 Service 只能从集群内访问。
  • NodePort - 使用 NAT 在集群中每个选定 Node 的相同端口上公开 Service 。使用<NodeIP>:<NodePort> 从集群外部访问 Service。是 ClusterIP 的超集。
  • LoadBalancer - 在当前云中创建一个外部负载均衡器(如果支持的话),并为 Service 分配一个固定的外部IP。是 NodePort 的超集。(常用)
  • ExternalName - 通过返回带有该名称的 CNAME 记录,使用任意名称(由 spec 中的externalName指定)公开 Service。不使用代理。这种类型需要kube-dns的v1.7或更高版本。

Service 通过一组 Pod 路由通信。Service 是一种抽象,它允许 Pod 死亡并在 Kubernetes 中复制,而不会影响应用程序。在依赖的 Pod (如应用程序中的前端和后端组件)之间进行发现和路由是由Kubernetes Service 处理的。

Service 匹配一组 Pod 是使用 标签(Label)和选择器(Selector), 它们是允许对 Kubernetes 中的对象进行逻辑操作的一种分组原语。标签(Label)是附加在对象上的键/值对,可以以多种方式使用:

  • 指定用于开发,测试和生产的对象
  • 嵌入版本标签
  • 使用 Label 将对象进行分类

标签(Label)可以在创建时或之后附加到对象上。他们可以随时被修改。

缩放你的应用

扩展 Deployment 将创建新的 Pods,并将资源调度请求分配到有可用资源的节点上,收缩 会将 Pods 数量减少至所需的状态。

执行滚动更新

滚动更新 允许通过使用新的实例逐步更新 Pod 实例,零停机进行 Deployment 更新。

默认情况下,更新期间不可用的 pod 的最大值和可以创建的新 pod 数都是 1。这两个选项都可以配置为(pod)数字或百分比。

配置

使用 ConfigMap 来配置 Redis

官网详细文档

真实世界的案例:使用 ConfigMap 来配置 Redis

首先创建一个配置模块为空的 ConfigMap,一个yaml文件:

example-redis-config.yaml

1
2
3
4
5
6
apiVersion: v1
kind: ConfigMap
metadata:
  name: example-redis-config
data:
  redis-config: ""

应用上面创建的 ConfigMap 以及 Redis pod 清单:

1
2
3
4
#应用ConfigMap
kubectl apply -f example-redis-config.yaml
#使用Redis pod清单创建Redis pod
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/config/redis-pod.yaml

redis pod清单

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: redis:5.0.4
    command:
      - redis-server
      - "/redis-master/redis.conf"
    env:
    - name: MASTER
      value: "true"
    ports:
    - containerPort: 6379
    resources:
      limits:
        cpu: "0.1"
    volumeMounts:
    - mountPath: /redis-master-data
      name: data
    - mountPath: /redis-master
      name: config
  volumes:
    - name: data
      emptyDir: {}
    - name: config
      configMap:
        name: example-redis-config
        items:
        - key: redis-config
          path: redis.conf
  • spec.volumes[1] 创建一个名为 config 的卷
  • spec.volumes[1].items[0] 下的 key 和 path 会将来自 example-redis-config ConfigMap 中的 redis-config 密钥公开在 config 卷上一个名为 redis.conf 的文件中
  • config 卷被 spec.containers[0].volumeMounts[1] 挂载在 /redis-master

这样做的最终效果是将上面 example-redis-config 配置中 data.redis-config 的数据作为 Pod 中的 /redis-master/redis.conf 公开

检查创建的对象

1
kubectl get pod/redis configmap/example-redis-config 

查看ConfigMap详细信息

1
kubectl describe configmap/example-redis-config

博客园 浅尝辄止~ yaml文件中的特殊符号|和>

使用 kubectl exec 进入 pod,运行 redis-cli 工具检查当前配置

1
kubectl exec -it redis -- redis-cli

查看 maxmemory

127.0.0.1:6379> CONFIG GET maxmemory

查看 maxmemory-policy

127.0.0.1:6379> CONFIG GET maxmemory-policy

向 example-redis-config ConfigMap 添加一些配置:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: ConfigMap
metadata:
  name: example-redis-config
data:
  redis-config: |
    maxmemory 2mb
    maxmemory-policy allkeys-lru     

应用更新的 ConfigMap

1
kubectl apply -f example-redis-config.yaml

确认 ConfigMap 已更新

1
kubectl describe configmap/example-redis-config

重新建立pod

1
2
kubectl delete pod redis
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/config/redis-pod.yaml

之后再检查redis配置就成功了

安全

在集群级别应用 Pod 安全标准

需要安装有KinD和kubectl

创建一个没有应用 Pod 安全标准的集群

1
kind create cluster --name psa-wo-cluster-pss --image kindest/node:v1.24.0

详见官网

无状态应用程序

公开外部 IP 地址以访问集群中应用程序

为一个在五个 pod 中运行的应用程序创建服务

在集群中运行 Hello World 应用程序

1
kubectl apply -f https://k8s.io/examples/service/load-balancer-example.yaml

显示有关 Deployment 的信息

1
2
kubectl get deployments hello-world
kubectl describe deployments hello-world

显示有关 ReplicaSet 对象的信息

1
2
kubectl get replicasets
kubectl describe replicasets

创建公开 Deployment 的 Service 对象

1
kubectl expose deployment hello-world --type=LoadBalancer --name=my-service

显示有关 Service 的信息

1
kubectl get services my-service

显示有关 Service 的详细信息

1
kubectl describe services my-service

显示的endpoints是pod内部地址。要验证这些是 Pod 地址,请输入以下命令:

1
kubectl get pods --output=wide

使用外部 IP 地址(LoadBalancer Ingress)访问 Hello World 应用程序

1
curl --noproxy "*" http://<external-ip>:<port>

清理现场

删除 Service

1
kubectl delete services my-service

删除正在运行 Hello World 应用程序的 Deployment、ReplicaSet 和 Pod

1
kubectl delete deployment hello-world

有状态的应用

StatefulSet 基础

Service 与 Pod 的 DNS

Service 与 Pod 的 DNS

Kubernetes 为 Service 和 Pod 创建 DNS 记录。 可以使用一致的 DNS 名称而非 IP 地址访问 Service。

无头服务(Headless Services)

无头服务(Headless Services)

不需要或不想要负载均衡,以及单独的 Service IP。指定 Cluster IP(spec.clusterIP)的值为 “None” 来创建 Headless Service。

持久卷

持久卷

持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class)来动态制备。 持久卷是集群资源,就像节点也是集群资源一样。

持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。PVC 申领可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载)

StatefulSet

StatefulSet

StatefulSet 是用来管理有状态应用的工作负载 API 对象。

StatefulSet 用来管理某 Pod 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。

和 Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID,这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。

如果希望使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。

创建 StatefulSet。

web.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

监视StatefulSet 的 Pod 的创建情况

1
kubectl get pods -w -l app=nginx

在另一个终端中,使用 kubectl apply 来创建定义在 web.yaml 中的 Headless Service 和 StatefulSe

1
kubectl apply -f web.yaml

获取 nginx Service

1
kubectl get service nginx

获取 web StatefulSet

1
kubectl get statefulset web

顺序创建 Pod

对于一个拥有 n 个副本的 StatefulSet,Pod 被部署时是按照 {0..n-1} 的序号顺序创建的

StatefulSet 中的 Pod

StatefulSet 中的每个 Pod 拥有一个唯一的顺序索引和稳定的网络身份标识

获取 StatefulSet 的 Pod

1
kubectl get pods -l app=nginx

这个标志基于 StatefulSet 控制器分配给每个 Pod 的唯一顺序索引,Pod 名称的格式为 <statefulset 名称>-<序号索引>

使用稳定的网络身份标识

每个 Pod 都拥有一个基于其顺序索引的稳定的主机名,显示主机名

1
for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done

通过对 Pod 的主机名执行 nslookup,你可以检查这些主机名在集群内部的 DNS 地址

1
kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

这将启动一个新的 Shell。在新 Shell 中运行

1
2
# 在 dns-test 容器 Shell 中运行以下命令
nslookup web-0.nginx

退出容器 Shell:exit

在一个终端中监视 StatefulSet 的 Pod

1
kubectl get pod -w -l app=nginx

在另一个终端中使用 kubectl delete 删除 StatefulSet 中所有的 Pod

1
kubectl delete pod -l app=nginx

StatefulSet 将重启它们。Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能发生改变

写入稳定的存储

获取 web-0 和 web-1 的 PersistentVolumeClaims

1
kubectl get pvc -l app=nginx

StatefulSet 控制器创建了两个 PersistentVolumeClaims, 绑定到两个 PersistentVolumes。这里是动态制备 PersistentVolume 卷,所有的 PersistentVolume 卷都是自动创建和绑定的。

NginX Web 服务器默认会加载位于 /usr/share/nginx/html/index.html 的 index 文件。 StatefulSet spec 中的 volumeMounts 字段保证了 /usr/share/nginx/html 文件夹由一个 PersistentVolume 卷支持。

Pod 的主机名写入它们的 index.html 文件并验证 NginX Web 服务器使用该主机名提供服务。

1
2
3
for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; **done**

在一个终端监视 StatefulSet 的 Pod

1
kubectl get pod -w -l app=nginx

在另一个终端删除 StatefulSet 所有的 Pod

1
kubectl delete pod -l app=nginx

等待所有 Pod 变成 Running 和 Ready 状态

验证所有 Web 服务器在继续使用它们的主机名提供服务

1
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done

返回结果和之前配置的一样,说明虽然 web-0 和 web-1 被重新调度了,但它们仍然继续监听各自的主机名,因为和它们的 PersistentVolumeClaim 相关联的 PersistentVolume 卷被重新挂载到了各自的 volumeMount 上。

扩容/缩容 StatefulSet

kubectl scale 或者 kubectl patch 来扩容/缩容一个 StatefulSet

扩容

在一个终端窗口监视 StatefulSet 的 Pod

1
kubectl get pods -w -l app=nginx

在另一个终端窗口使用 kubectl scale 扩展副本数为 5

1
kubectl scale sts web --replicas=5

缩容

在另一个终端使用 kubectl patch 将 StatefulSet 缩容回三个副本

1
kubectl patch sts web -p '{"spec":{"replicas":3}}'

顺序终止 Pod:控制器会按照与 Pod 序号索引相反的顺序每次删除一个 Pod。在删除下一个 Pod 前会等待上一个被完全关闭。

更新 StatefulSet

从 Kubernetes 1.7 版本开始,StatefulSet 控制器支持自动更新。 更新策略由 StatefulSet API 对象的 spec.updateStrategy 字段决定。这个特性能够用来更新一个 StatefulSet 中 Pod 的的容器镜像、资源请求和限制、标签和注解。

RollingUpdate 更新策略是 StatefulSet 默认策略。

滚动更新

RollingUpdate 更新策略会更新一个 StatefulSet 中的所有 Pod,采用与序号索引相反的顺序并遵循 StatefulSet 的保证。

对 web StatefulSet 应用 Patch 操作来应用 RollingUpdate 更新策略

1
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate"}}}'

在一个终端窗口中对 web StatefulSet 执行 patch 操作来再次改变容器镜像

1
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"gcr.io/google_containers/nginx-slim:0.8"}]'

StatefulSet 里的 Pod 采用和序号相反的顺序更新。在更新下一个 Pod 前,StatefulSet 控制器终止每个 Pod 并等待它们变成 Running 和 Ready。

顺序后继者变成 Running 和 Ready 之前 StatefulSet 控制器不会更新下一个 Pod,但它仍然会重建任何在更新过程中发生故障的 Pod,使用的是它们当前的版本

获取 Pod 来查看它们的容器镜像:

1
for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done

分段更新

使用 RollingUpdate 更新策略的 partition 参数来分段更新一个 StatefulSet。

分段的更新将会使 StatefulSet 中的其余所有 Pod 保持当前版本的同时允许改变 StatefulSet 的 .spec.template

对 web StatefulSet 执行 Patch 操作为 updateStrategy 字段添加一个分区

1
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'

再次 Patch StatefulSet 来改变容器镜像

1
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.7"}]'

删除 StatefulSet 中的 Pod

1
kubectl delete pod web-2

等待 Pod 变成 Running 和 Ready,获取 Pod 的容器镜像

1
kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'

虽然更新策略是 RollingUpdate,StatefulSet 还是会使用原始的容器恢复 Pod。 这是因为 Pod 的序号比 updateStrategy 指定的 partition 更小

金丝雀发布

你可以通过减少上文指定的 partition 来进行金丝雀发布,以此来测试你的程序的改动

通过 patch 命令修改 StatefulSet 来减小分区

1
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'

等待 web-2 变成 Running 和 Ready,获取 Pod 的容器

1
kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'

当你改变 partition 时,StatefulSet 会自动更新 web-2 Pod,这是因为 Pod 的序号大于或等于 partition

删除 web-1 Pod

1
kubectl delete pod web-1

等待 web-1 变成 Running 和 Ready,获取 web-1 Pod 的容器镜像

1
kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'

web-1 被按照原来的配置恢复,因为 Pod 的序号小于分区。当指定了分区时,如果更新了 StatefulSet 的 .spec.template,则所有序号大于或等于分区的 Pod 都将被更新。 如果一个序号小于分区的 Pod 被删除或者终止,它将被按照原来的配置恢复。

分阶段的发布

可以使用类似金丝雀发布的方法执行一次分阶段的发布 (例如一次线性的、等比的或者指数形式的发布)。要执行一次分阶段的发布,你需要设置 partition 为希望控制器暂停更新的序号。

将分区设置为 0

1
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'

等待 StatefulSet 中的所有 Pod 变成 Running 和 Ready

1
kubectl get pod -l app=nginx -w

获取 StatefulSet 中 Pod 的容器镜像详细信息

1
for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done

将 partition 改变为 0 以允许 StatefulSet 继续更新过程

OnDelete 策略

OnDelete 更新策略实现了传统(1.7 之前)行为,它也是默认的更新策略。 当你选择这个更新策略并修改 StatefulSet 的 .spec.template 字段时,StatefulSet 控制器将不会自动更新 Pod。

删除 StatefulSet

StatefulSet 同时支持级联和非级联删除。使用非级联方式删除 StatefulSet 时,StatefulSet 的 Pod 不会被删除。使用级联删除时,StatefulSet 和它的 Pod 都会被删除。

非级联删除

在一个终端窗口监视 StatefulSet 中的 Pod

1
kubectl get pods -w -l app=nginx

使用 kubectl delete 删除 StatefulSet。请确保提供了 –cascade=orphan 参数给命令。这个参数告诉 Kubernetes 只删除 StatefulSet 而不要删除它的任何 Pod。

1
kubectl delete statefulset web --cascade=orphan

虽然 web 已经被删除了,但所有 Pod 仍然处于 Running 和 Ready 状态。 删除 web-0

1
kubectl delete pod web-0

获取 StatefulSet 的 Pod

1
kubectl get pods -l app=nginx

由于 web StatefulSet 已经被删除,web-0 没有被重新启动

在一个终端监控 StatefulSet 的 Pod

1
kubectl get pods -w -l app=nginx

在另一个终端里重新创建 StatefulSet

1
kubectl apply -f web.yaml

返回

statefulset.apps/web created
service/nginx unchanged

service/nginx unchanged表示 kubernetes 进行了一次创建 nginx headless Service 的尝试,尽管那个 Service 已经存在

由于你重新创建的 StatefulSet 的 replicas 等于 2,所以如果0和1没有创建,则创建,如果已经是run则不管。大于等于2的pod将被删除

再看看被 Pod 的 Web 服务器加载的 index.html 的内容

1
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done

返回

1
2
web-0
web-1

尽管你同时删除了 StatefulSet 和 web-0 Pod,但它仍然使用最初写入 index.html 文件的主机名进行服务。 这是因为 StatefulSet 永远不会删除和一个 Pod 相关联的 PersistentVolume 卷。当你重建这个 StatefulSet 并且重新启动了 web-0 时,它原本的 PersistentVolume 卷会被重新挂载。

级联删除

在一个终端窗口监视 StatefulSet 里的 Pod

1
kubectl get pods -w -l app=nginx

在另一个窗口中再次删除这个 StatefulSet。这次省略 –cascade=orphan 参数。

1
kubectl delete statefulset web

等待所有的 Pod 变成 Terminating 状态,这些 Pod 按照与其序号索引相反的顺序每次终止一个。 在终止一个 Pod 前,StatefulSet 控制器会等待 Pod 后继者被完全终止。

尽管级联删除会删除 StatefulSet 及其 Pod,但级联不会删除与 StatefulSet 关联的 Headless Service。你必须手动删除 nginx Service。

1
kubectl delete service nginx

再一次重新创建 StatefulSet 和 headless Service:

1
kubectl apply -f web.yaml

当 StatefulSet 所有的 Pod 变成 Running 和 Ready 时,获取它们的 index.html 文件的内容:

1
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done

即使你已经删除了 StatefulSet 和它的全部 Pod,这些 Pod 将会被重新创建并挂载它们的 PersistentVolume 卷,并且 web-0 和 web-1 将继续使用它的主机名提供服务。

最后删除 nginx service

1
kubectl delete service nginx

删除 web StatefulSet:

1
kubectl delete statefulset web
Pod 管理策略

对于某些分布式系统来说,StatefulSet 的顺序性保证是不必要和/或者不应该的。这些系统仅仅要求唯一性和身份标志。在 Kubernetes 1.7 中 针对 StatefulSet API 对象引入了 .spec.podManagementPolicy。 此选项仅影响扩缩操作的行为。更新不受影响。

OrderedReady Pod 管理策略

OrderedReady Pod 管理策略是 StatefulSet 的默认选项。它告诉 StatefulSet 控制器遵循上文展示的顺序性保证。

Parallel Pod 管理策略

Parallel Pod 管理策略告诉 StatefulSet 控制器并行的终止所有 Pod, 在启动或终止另一个 Pod 前,不必等待这些 Pod 变成 Running 和 Ready 或者完全终止状态。

web-parallel.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  podManagementPolicy: "Parallel"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

这份清单和你在上文下载的完全一样,只是 web StatefulSet 的 .spec.podManagementPolicy 设置成了 Parallel

在一个终端窗口监视 StatefulSet 中的 Pod

1
kubectl get pod -l app=nginx -w

在另一个终端窗口创建清单中的 StatefulSet 和 Service

1
kubectl apply -f web-parallel.yaml

StatefulSet 控制器同时启动了 web-0 和 web-1

保持第二个终端打开,并在另一个终端窗口中扩容 StatefulSet:

1
kubectl scale statefulset/web --replicas=4

StatefulSet 启动了两个新的 Pod,而且在启动第二个之前并没有等待第一个变成 Running 和 Ready 状态。

清理现场

删除statefulset

1
2
kubectl delete sts web
# sts is an abbreviation for statefulset

删除 nginx Service

1
kubectl delete svc nginx

删除本教程中用到的 PersistentVolume 卷的持久化存储介质

获取pvc的name

1
kubectl get pvc

删除pvc

1
kubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4

查看pv和pvc的状态

1
2
3
kubectl get pv

kubectl get pvc

Services

使用源 IP

运行在 Kubernetes 集群中的应用程序通过 Service 抽象发现彼此并相互通信,它们也用 Service 与外部世界通信。本文解释了发送到不同类型 Service 的数据包的源 IP 会发生什么情况,以及如何根据需要切换此行为。

术语表

  • NAT:网络地址转换
  • Source NAT:替换数据包上的源 IP;在本页面中,这通常意味着替换为节点的 IP 地址
  • Destination NAT:替换数据包上的目标 IP;在本页面中,这通常意味着替换为 Pod 的 IP 地址
  • VIP:一个虚拟 IP 地址,例如分配给 Kubernetes 中每个 Service 的 IP 地址
  • Kube-proxy:一个网络守护程序,在每个节点上协调 Service VIP 管理

示例使用一个小型 nginx Web 服务器,服务器通过 HTTP 标头返回它接收到的请求的源 IP。

1
kubectl create deployment source-ip-app --image=registry.k8s.io/echoserver:1.4
Type=ClusterIP 类型 Service 的源 IP

在 iptables 模式(默认)下运行 kube-proxy,则从集群内发送到 ClusterIP 的数据包永远不会进行源 NAT。 可以通过在运行 kube-proxy 的节点上获取 http://localhost:10249/proxyMode 来查询 kube-proxy 模式。

查看节点ip

1
kubectl get nodes -o=wide

查看节点代理模式

1
curl http://$节点ip:10249/proxyMode

输出为:iptables

在源 IP 应用程序上创建 Service 来测试源 IP 保留

1
kubectl expose deployment source-ip-app --name=clusterip --port=80 --target-port=8080

查看服务

1
kubectl get svc clusterip

从同一集群中的 Pod 中访问 ClusterIP:

1
kubectl run busybox -it --image=busybox:1.28 --restart=Never --rm

打开一个控制台,在该 Pod 中运行命令:

1
2
# 从 “kubectl run” 的终端中运行
ip addr

使用 wget 查询本地 Web 服务器

# 将 “10.0.170.92” 替换为 Service 中名为 “clusterip” 的 IPv4 地址
wget -qO - 10.0.170.92

不管客户端 Pod 和服务器 Pod 位于同一节点还是不同节点,client_address 始终是客户端 Pod 的 IP 地址

Type=NodePort 类型 Service 的源 IP

默认情况下,发送到 Type=NodePort 的 Service 的数据包会经过源 NAT 处理。

创建一个 NodePort 的 Service 来测试这点

1
kubectl expose deployment source-ip-app --name=nodeport --port=80 --target-port=8080 --type=NodePort

设置环境变量

1
2
NODEPORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services nodeport)
NODES=$(kubectl get nodes -o jsonpath='{ $.items[*].status.addresses[?(@.type=="InternalIP")].address }')

从集群外部访问 Service

1
for node in $NODES; do curl -s $node:$NODEPORT | grep -i client_address; done

这些并不是正确的客户端 IP,它们是集群的内部 IP。

  • 客户端发送数据包到 node2:nodePort
  • node2 使用它自己的 IP 地址替换数据包的源 IP 地址(SNAT)
  • node2 将数据包上的目标 IP 替换为 Pod IP
  • 数据包被路由到 node1,然后到端点
  • Pod 的回复被路由回 node2
  • Pod 的回复被发送回给客户端

为避免这种情况,Kubernetes 有一个特性可以保留客户端源 IP。 如果将 service.spec.externalTrafficPolicy 设置为 Local, kube-proxy 只会将代理请求代理到本地端点,而不会将流量转发到其他节点。 这种方法保留了原始源 IP 地址。如果没有本地端点,则发送到该节点的数据包将被丢弃, 因此你可以在任何数据包处理规则中依赖正确的源 IP,你可能会应用一个数据包使其通过该端点。

设置 service.spec.externalTrafficPolicy 字段如下

1
kubectl patch svc nodeport -p '{"spec":{"externalTrafficPolicy":"Local"}}'

现在,重新运行测试

1
for node in $NODES; do curl --connect-timeout 1 -s $node:$NODEPORT | grep -i client_address; done

只从运行端点 Pod 的节点得到了回复,这个回复有正确的客户端 IP

  • 客户端将数据包发送到没有任何端点的 node2:nodePort
  • 数据包被丢弃
  • 客户端发送数据包到必有端点的 node1:nodePort
  • node1 使用正确的源 IP 地址将数据包路由到端点
Type=LoadBalancer 类型 Service 的源 IP

默认情况下,发送到 Type=LoadBalancer 的 Service 的数据包经过源 NAT处理,因为所有处于 Ready 状态的可调度 Kubernetes 节点对于负载均衡的流量都是符合条件的。 因此,如果数据包到达一个没有端点的节点,系统会将其代理到一个带有端点的节点,用该节点的 IP 替换数据包上的源 IP

你可以通过负载均衡器上暴露 source-ip-app 进行测试

1
kubectl expose deployment source-ip-app --name=loadbalancer --port=80 --target-port=8080 --type=LoadBalancer

打印 Service 的 IP 地址

1
kubectl get svc loadbalancer

接下来,发送请求到 Service 的 的外部 IP

1
curl --noproxy "*" serviceip

minikube 会使得 LoadBalancer 则会出现 External-IP 一直处于 pending 官方给出的解决办法是添加tunnel,百度 青柠姑娘17,详见官网

这里也需要注意curl不使用代理–noproxy “*”

将相同的 service.spec.externalTrafficPolicy 字段设置为 Local, 故意导致健康检查失败,从而强制没有端点的节点把自己从负载均衡流量的可选节点列表中删除。

清理现场

删除 Service:

1
kubectl delete svc -l app=source-ip-app

删除 Deployment、ReplicaSet 和 Pod:

1
kubectl delete deployment source-ip-app

使用 Service 连接到应用

Kubernetes 连接容器的模型

Kubernetes 假设 Pod 可与其它 Pod 通信,不管它们在哪个主机上。 Kubernetes 给每一个 Pod 分配一个集群私有 IP 地址,所以没必要在 Pod 与 Pod 之间创建连接或将容器的端口映射到主机端口。 这意味着同一个 Pod 内的所有容器能通过 localhost 上的端口互相连通,集群中的所有 Pod 也不需要通过 NAT 转换就能够互相看到。

在集群中暴露 Pod

run-my-nginx.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80

容器端口的规约containerPort: 80使得可以从集群中任何一个节点来访问它。检查节点,该 Pod 正在运行:

1
2
kubectl apply -f ./run-my-nginx.yaml
kubectl get pods -l run=my-nginx -o wide

检查 Pod 的 IP 地址:

1
kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs

能够通过 ssh 登录到集群中的任何一个节点上,并使用诸如 curl 之类的工具向这两个 IP 地址发出查询请求。

容器 不会 使用该节点上的 80 端口,也不会使用任何特定的 NAT 规则去路由流量到 Pod 上。 这意味着可以在同一个节点上运行多个 Nginx Pod,使用相同的 containerPort,并且可以从集群中任何其他的 Pod 或节点上使用 IP 的方式访问到它们。

创建 Service

Kubernetes Service 是集群中提供相同功能的一组 Pod 的抽象表达。 当每个 Service 创建时,会被分配一个唯一的 IP 地址(也称为 clusterIP)。 这个 IP 地址与 Service 的生命周期绑定在一起,只要 Service 存在,它就不会改变。 可以配置 Pod 使它与 Service 进行通信,Pod 知道与 Service 通信将被自动地负载均衡到该 Service 中的某些 Pod 上。

使用 kubectl expose 命令为 2个 Nginx 副本创建一个 Service:

1
kubectl expose deployment/my-nginx

这等价于使用 kubectl create -f 命令及如下的 yaml 文件创建:

nginx-svc.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx

查看你的 Service 资源:

1
kubectl get svc my-nginx

一个 Service 由一组 Pod 提供支撑。这些 Pod 通过 EndpointSlices 暴露出来。 Service Selector 将持续评估,结果被 POST 到使用标签与该 Service 连接的一个 EndpointSlice。 当 Pod 终止后,它会自动从包含该 Pod 的 EndpointSlices 中移除。 新的能够匹配上 Service Selector 的 Pod 将自动地被为该 Service 添加到 EndpointSlice 中。

检查 Endpoint,注意到 IP 地址与在第一步创建的 Pod 是相同的。

1
2
3
kubectl describe svc my-nginx

kubectl get endpointslices -l kubernetes.io/service-name=my-nginx

现在,你应该能够从集群中任意节点上使用 curl 命令向 <CLUSTER-IP>:<PORT> 发送请求以访问 Nginx Service。

访问 Service

环境变量

当 Pod 在节点上运行时,kubelet 会针对每个活跃的 Service 为 Pod 添加一组环境变量。

检查正在运行的 Nginx Pod 的环境变量

1
kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE

能看到环境变量中并没有你创建的 Service 相关的值。这是因为副本的创建先于 Service。调度器可能会将所有 Pod 部署到同一台机器上,如果该机器宕机则整个 Service 都会离线。

先终止这 2 个 Pod,然后等待 Deployment 去重新创建它们。 这次 Service 会 先于 副本存在。这将实现调度器级别的 Pod 按 Service 分布(假定所有的节点都具有同样的容量),并提供正确的环境变量:

1
2
3
kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2;

kubectl get pods -l run=my-nginx -o wide

再检查Pod环境变量

kubectl exec my-nginx-df7bbf6f5-252h7  -- printenv | grep SERVICE

可以看到有了MY_NGINX_SERVICE_HOST和MY_NGINX_SERVICE_PORT环境变量

DNS

Kubernetes 提供了一个自动为其它 Service 分配 DNS 名字的 DNS 插件 Service,通过如下命令检查它是否在工作

1
kubectl get services kube-dns --namespace=kube-system

让我们运行另一个 curl 应用来进行测试

1
kubectl run curl --image=radial/busyboxplus:curl -i --tty

在curl里面执行命令

1
执行命令 nslookup my-nginx
保护 Service

到现在为止,我们只在集群内部访问了 Nginx 服务器。在将 Service 暴露到因特网之前,我们希望确保通信信道是安全的。 为实现这一目的,需要:

  • 用于 HTTPS 的自签名证书(除非已经有了一个身份证书)
  • 使用证书配置的 Nginx 服务器
  • 使 Pod 可以访问证书的 Secret

获取相关文件

1
git clone https://github.com/kubernetes/examples.git

进入相关目录

1
2
3
cd example/staging/https-nginx
make keys KEY=/tmp/nginx.key CERT=/tmp/nginx.crt
kubectl create secret tls nginxsecret --key /tmp/nginx.key --cert /tmp/nginx.crt

查看secrets

1
kubectl get secrets

创建configmap

1
kubectl create configmap nginxconfigmap --from-file=default.conf

查看configmap

1
kubectl get configmaps

现在修改 Nginx 副本以启动一个使用 Secret 中的证书的 HTTPS 服务器以及相应的用于暴露其端口(80 和 443)的 Service:

nginx-secure-app.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 80
    protocol: TCP
    name: http
  - port: 443
    protocol: TCP
    name: https
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 1
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      volumes:
      - name: secret-volume
        secret:
          secretName: nginxsecret
      - name: configmap-volume
        configMap:
          name: nginxconfigmap
      containers:
      - name: nginxhttps
        image: bprashanth/nginxhttps:1.0
        ports:
        - containerPort: 443
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/ssl
          name: secret-volume
        - mountPath: /etc/nginx/conf.d
          name: configmap-volume
  • 将 Deployment 和 Service 的规约放在了同一个文件中
  • Nginx 服务器通过 80 端口处理 HTTP 流量,通过 443 端口处理 HTTPS 流量,而 Nginx Service 则暴露了这两个端口
  • 每个容器能通过挂载在 /etc/nginx/ssl 的卷访问秘钥。卷和密钥需要在 Nginx 服务器启动 之前 配置好。

清理之前的部署,启动nginx-secure-app.yaml

1
kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml

这时,你可以从任何节点访问到 Nginx 服务器

1
2
3
kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs

kubectl attach curl -c curl -i -t

在curl控制台里面

1
curl -k https://172.17.0.3

注意最后一步我们是如何提供 -k 参数执行 curl 命令的,这是因为在证书生成时, 我们不知道任何关于运行 nginx 的 Pod 的信息,所以不得不在执行 curl 命令时忽略 CName 不匹配的情况。

通过创建 Service,我们连接了在证书中的 CName 与在 Service 查询时被 Pod 使用的实际 DNS 名字。

暴露 Service

对应用的某些部分,你可能希望将 Service 暴露在一个外部 IP 地址上。

Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。 在上一段创建的 Service 使用了 NodePort,因此,如果你的节点有一个公网 IP,那么 Nginx HTTPS 副本已经能够处理因特网上的流量。

1
kubectl get svc my-nginx -o yaml | grep nodePort -C 5

让我们重新创建一个 Service 以使用云负载均衡器。 将 my-nginx Service 的 Type 由 NodePort 改成 LoadBalancer:

1
2
kubectl edit svc my-nginx
kubectl get svc my-nginx

访问服务

1
curl --noproxy "*" https://10.111.170.212 -k
 |