引言

本文探讨了k8s核心技术,包括ControllerServicePodyamlIngress等。


  1. k8s概念和架构
  2. 从零搭建k8s集群
  3. k8s核心技术

命令行工具kubectl

可以使用kubectl管理你k8s集群。在从零搭建k8s集群

sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config # kubeadm 安装k8s
cp kube_config_rancher-cluster.yml ~/.kube/config # rke安装k8s

都有上面这样一个复制文件的操作。因为kubectl会在$HOME/.kube目录中查找名为config的配置文件。

语法

kubectl [command] [TYPE] [NAME] [flags]
  • command : 指定要对一个或多个资源执行的操作,例如 create、get、describe、delete

  • type : 指定资源类型。可以用kubectl api-resources查看所有资源类型。资源类型不区分大小写, 可以指定单数、复数或缩写形式。例如,以下命令输出相同的结果:

    kubectl get pod pod1
    kubectl get pods pod1
    kubectl get po pod1
    
  • name : 指定资源的名称。名称区分大小写。 如果省略名称,则显示所有资源的详细信息 kubectl get pods

    • 按类型和名称指定资源:
      • 对所有类型相同的资源进行分组,执行:TYPE name1 name2 name<#>
        • 例子:kubectl get pod example-pod1 example-pod2
      • 分别指定多个资源类型: TYPE1/name1 TYPE1/name2 TYPE2/name3 TYPE<#>/name<#>
        • 例子:kubectl get pod/example-pod1 replicationcontroller/example-rc1
    • 用一个或多个文件指定资源:-f file1 -f file2 -f file<#>
      • 建议使用YAML格式而不是json,因为前者更容易使用,特别是用于配置文件时。例如:kubectl get -f ./pod.yaml
  • flags : 指定可选的参数。例如,可以使用 -s-server 参数指定 Kubernetes API 服务器的地址和端口。

使用kubectl --help 可以看到常用命令信息:

kubectl --help
kubectl controls the Kubernetes cluster manager.

 Find more information at: https://kubernetes.io/docs/reference/kubectl/overview/

Basic Commands (Beginner):
  create        通过文件名或标准输入创建资源
  expose       将一个资源公开为一个新的Service
  run           在集群中运行一个特定的进行
  set           在对上上设置特定的功能

Basic Commands (Intermediate):
  explain       资源的文档参考资料
  get           显示一个或多个资源
  edit          使用默认的编辑器编辑一个资源
  delete       通过文件名、标准输入、资源名称或标签选择器来删除资源

Deploy Commands:
  rollout       管理资源的发布
  scale         扩容或缩容pod数量,Deployment、ReplicaSet、RC或Job
  autoscale   创建一个自动选择扩容或缩容并设置Pod数量

Cluster Management Commands:
  certificate   修改证书资源
  cluster-info  显示集群信息
  top           显示资源(CPU/Memory/Storage) 使用
  cordon       标记节点不可调度
  uncordon      标记节点可调度
  drain        驱逐节点上的应用,准备下线维护
  taint        修改节点taint标记

Troubleshooting and Debugging Commands:
  describe      显示特定资源或资源组的详细信息
  logs          在一个pod种打印一个容器的日志
  attach        附加到一个运行的容器
  exec          在容器中执行命令
  port-forward  转发一个或多个本地端口到一个pod
  proxy         运行一个proxy到API Server
  cp            从容器中拷贝文件或目录/拷贝文件或目录到容器中
  auth         检查授权

Advanced Commands:
  diff         显示当前版本和将要应用版本的区别
  apply       通过文件名或标准输入配置资源应用
  patch        使用合并补丁策略更新资源上的字段
  replace      通过文件名或标准输入替换一个资源
  wait          实验性的:在一个或多个资源上等待特定条件 
  convert       不同API版本之间转换配置文件
  kustomize     通过目录或远程url构建一个kustomization 目标

Settings Commands:
  label        更新资源上的标签
  annotate      更新资源上的注释
  completion    用于实现kubectl工具自动补全

Other Commands:
  alpha         用户实验特性
  api-resources 打印受支持的API资源
  api-versions  打印受支持的API版本
  config       修改kubeconfig文件
  plugin       运行一个命令行查看
  version       打印客户端和服务端版本信息

Usage:
  kubectl [flags] [options]

Use "kubectl <command> --help" for more information about a given command.
Use "kubectl options" for a list of global command-line options (applies to all commands).

资源编排(yaml)介绍

k8s集群中对资源管理和资源你对象编排部署都可以通过声明样式(YAML)文件来解决,也就是可以把需要对资源对象操作编辑到YAML格式文件中,我们把这种文件叫做资源清单文件,通过kubectl命令直接使用资源清单文件就可以实现对大量的资源对象进行编排部署。

YAML文件书写格式

基本语法:

  • 使用(一般两个)空格作为缩进
  • 相同层级的元素左侧对其
  • 使用#标识注释
  • 使用---表示新的YAML文件

YAML文件组成部分

以一个例子来描述:

apiVersion: apps/v1 # 创建该资源(对象)所使用的Kubernetes API的版本
kind: Deployment # 资源类型
metadata: # 资源元数据:帮助唯一标识资源的一些数据
  name: nginx-deployment # 资源名称
  namespace: default # 属于的命名空间
spec: # 资源规约(规格)
  selector: # 标签选择器
    matchLabels:  # 匹配的标签
      app: nginx # 标签的形式
  replicas: 2 # 副本数量,指定需要运行两个pod
# 上面是控制器的定义
# 下面是被控制的对象
  template: # pod模板
    metadata: # pod元数据
      labels: # 标签,支持高效的查询和监听
        app: nginx  # 标签通常是键值对的形式
    spec: # pod规格
      containers: # 容器配置
      - name: nginx # 名称
        image: nginx:1.14.2 #镜像
        ports: # 端口
        - containerPort: 80 # 容器端口

生成YAML文件

从上小节我们可以大概了解yaml文件里的东西是干什么的。但是如果让你去写一个这样的文件是很难且容易出错的。所以k8s提供了快速编写yaml文件的方法。

使用kubectl create命令生成yaml文件

kubectl create deployment web --image=nginx \
 -o yaml  # 输出yaml文件 \
 --dry-run=client \ # 并不真正执行创建操作,仅用于生成文件
 > web.yaml # 输出到web.yaml中

执行结果如下:

yjw@rancher1:~/temp$ kubectl create deployment web --image=nginx -o yaml --dry-run=client
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: web
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: web
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

使用kubectl get命令导出yaml文件

使用kubectl get命令导出运行的deployment配置,首先查看有没有运行的:

yjw@rancher1:~/temp$ kubectl get deploy
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
nginx            1/1     1            1           3m14s

然后执行下面的命令导出yaml文件到nginx.yaml中:

kubectl get deploy nginx -o=yaml > nginx.yaml # 

查看导出的文件:

yjw@rancher1:~/temp$ cat nginx.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    field.cattle.io/publicEndpoints: '[{"addresses":["192.168.1.6"],"port":30727,"protocol":"TCP","serviceName":"default:nginx","allNodes":true}]'
  creationTimestamp: "2021-04-11T05:46:57Z"
  generation: 2
  labels:
    app: nginx
  managedFields:
  - apiVersion: apps/v1
    fieldsType: FieldsV1
 ... # 文件太长

在配置文件中我们知道replicas: 2 是指定需要运行的pod数量,那pod具体是什么呢

pod

pod概述

Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元,是资源对象模型中由用户创建或部署的最小资源对象模型,也是在k8s上运行容器化应用的资源对象。

其他的资源对象都是用来支撑或扩展pod对象功能的,比如控制器对象时用来管控pod对象的,Service或Ingress资源是用来暴露pod引用对象的,PersistentVolume资源对象时用来为pod提供存储等。

k8s不会直接处理容器(container),而是pod。

一个pod包含一个或多个应用容器, 这些容器是相对紧密的耦合在一起的。
pod中所有容器具有相同的IP地址和端口空间(共享网络)。
pod被认为是临时的。可替代的实体,需要的话可以被丢弃或替换。

每个pod都有一个特殊的被称为根容器的Pause容器。除了Pause容器,每个pod还包含一个或多个紧密相关的用户业务容器。

pod存在意义

我们知道docker也能创建容器,一个docker对应一个容器,一个容器有一个进程,一个进程运行一个容器,即一个容器运行一个应用程序。为什么k8s不直接管理docker容器,而是提出pod概念呢。

pod是多进程设计,可以运行多个应用程序。而同一pod中网路是共享的,所以易于实现pod内多个应用程序的交互。

使用pod的好处有以下四点:

  • 透明性:使pod内的容器对基础设置可见,使得基础设施能够向这些容器提供服务,例如流程管理和资源监控
  • 解耦软件依赖:可以独立地对单个容器进行版本化、重建和重新部署
  • 易用性:用户无须运行自由进程管理器,也不需要担心信号和退出代码的事务传播等。
  • 效率:由于基础设施将承担更多的职责,因此容器可以更加轻量

pod两种实现机制

像pod这样一个东西,本身是一个逻辑概念。那么在机器上,它究竟是如何实现的。本节就来探讨这个问题。

要实现pod这个东西,核心就在于如何让一个pod里的多个容器之间高效的共享某些资源和数据。

因为容器之间原本是诶Linux Namespace和cgroups隔离开的,所以现在要解决的是如何打破这个隔离,然后共享某些信息。这就是pod的设计要解决的核心问题所在。

所以说具体的解法分为两个部分:网络和存储。

共享网络

Pause容器就是为解决pod中的网络问题而生的。

假设现在有一个 Pod,其中包含了一个容器 A 和一个容器 B,它们两个要共享 Network Namespace。在 Kubernetes 里的解法是这样的:它会在每个 Pod 里,额外起一个 Infra container 小容器(就是Pause容器)来共享整个 Pod 的 Network Namespace。

在这里插入图片描述

Infra container 是一个非常小的镜像,是一个C语言写的、永远处于“暂停”(pause)状态的容器。由于有了这样一个 Infra container 之后,其他所有容器都会通过 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。

所以说一个 Pod 里面的所有容器,它们看到的网络视图是完全一样的。即:它们看到的网络设备、IP地址、Mac地址等等,跟网络相关的信息,其实全是一份,这一份都来自于 Pod 第一次创建的这个 Infra container。这就是 Pod 解决网络共享的一个解法。

所以整个pod里面,必然是Infra container第一个启动。且整个pod生命周期等同于Infra container的生命周期,而与容器A和B无关的。这也是为什么在k8s里面,允许单独更新pod里的某一个镜像,这是非常重要的一个设计。

共享存储

pod上存储是临时的,会随pod一起消失。但有些数据,比如日志数据或业务数据等,需要在pod上存储更长的时间,或者在pod间传递数据,存储卷(Volumn)的概念便支持了这种需求。

Pod中的应用容器可以共享Volumn。Volume能够保证pod重启时使用的数据不丢失(比如说某台机器宕机了,运行在它上面的pod就需要转移到其他机器上,此时可以读取之前的数据就很重要)。

pod 镜像拉取策略

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.14
      imagePullPolicy: Always

其中imagePullPolicy就是镜像拉取策略,现在有三种策略:

  • IfNotPresent : 默认值,镜像在宿主机上不存在时才拉取
  • Always : 每次创建pod都会重新拉取一次镜像
  • Never : pod永远不会主动拉取这个镜像

pod资源限制

假设pod需要2C/4G (2核4G内存),而现在有三个节点:

  • node1 : 2C/4G
  • node2 : 4C/16G
  • node3: 1C/2G

那么只能部署到node1和node2上,这种就是资源限制。

在k8s中,通过yaml文件可以这样声明资源限制:

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-ctr
    image: polinux/stress
    resources:
      limits:
        memory: "200Mi"
        cpu: "500m"
      requests:
        memory: "100Mi"
        cpu: "250m"

这里包含了请求(request)和限制(limit)。可以保证容器拥有它请求的资源,但不允许超过限制的资源。

本例中容器请求100M的内存和250m的CPU。 同时限制最多分配200M内存和500m的CPU。

1核 CPU可以理解为1000M 大小。

当节点拥有足够的可用内存时,容器可以使用其请求的内存。 但是,容器不允许使用超过其限制的内存。 如果容器分配的内存超过其限制,该容器会成为被终止的候选容器。 如果容器继续消耗超出其限制的内存,则终止容器。

下面以官网的示例为例,看一下当容器尝试分配超过其限制的内存时会怎么样:

yjw@rancher1:~/temp$ cat memory-request-limit.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: memory-demo-2
  # namespace: mem-example 注释掉,使用默认命名空间
spec:
  containers:
  - name: memory-demo-2-ctr
    image: polinux/stress
    resources:
      requests:
        memory: "50Mi"
      limits:
        memory: "100Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"] # 尝试分配250M内存

应用它:

kubectl apply -f memory-request-limit.yaml

可以查看这个pod,发现它在8分钟内重启了6次,并且还是CrashLoopBackOff状态。

yjw@rancher1:~/temp$ kubectl get pod memory-demo-2
NAME            READY   STATUS             RESTARTS   AGE
memory-demo-2   0/1     CrashLoopBackOff   6          8m23s

执行kubectl describe nodes可以看到很多关于:

Warning  SystemOOM  9m20s  kubelet  System OOM encountered, victim process: stress, pid: 115325
  Warning  SystemOOM  9m15s  kubelet  System OOM encountered, victim process: stress, pid: 115459
  Warning  SystemOOM  8m48s  kubelet  System OOM encountered, victim process: stress, pid: 115681
  Warning  SystemOOM  8m16s  kubelet  System OOM encountered, victim process: stress, pid: 115930
  Warning  SystemOOM  7m19s  kubelet  System OOM encountered, victim process: stress, pid: 116314
  Warning  SystemOOM  5m52s  kubelet  System OOM encountered, victim process: stress, pid: 116906
  Warning  SystemOOM  3m5s   kubelet  System OOM encountered, victim process: stress, pid: 117918

可以看到容器由于内存溢出而被干掉的记录。最后别忘了执行命令删掉这个pod:

yjw@rancher1:~/temp$ kubectl delete pod memory-demo-2
pod "memory-demo-2" deleted

pod重启策略

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

其中restartPolicy是pod重启策略,取值有:

  • Always : 默认值,当容器终止退出后,总是重启容器。适用于一直需要提供服务的场景
  • OnFailure : 当容器异常退出时,才重启容器
  • Never : 当容器终止退出,从不重启容器。适用于一次性任务

上面是一个 Job 配置示例。它负责计算 π 到小数点后 2000 位,并将结果打印出来。
这种任务只需要运行一次,因此我们指定为Never

pod健康检查

许多长时间运行的应用程序最终会过渡到断开的状态,除非重新启动,否则无法恢复。 Kubernetes 提供了存活探测器来发现并补救这种情况。

kubelet 使用存活探测器来知道什么时候要重启容器。 例如,存活探测器可以捕捉到死锁(应用程序在运行,但是无法继续执行后面的步骤)。 这样的情况下重启容器有助于让应用程序在有问题的情况下更可用。

k8s有两种检查机制:

  • livenessProbe(存活检查)
    • 如果检查失败,将杀死容器,根据pod的restartPolicy来操作
  • readinessProbe(就绪检查)
    • 如果检查失败,k8s会把pod从service endpoints中剔除

而Probe又支持三种检查方法。

exec

执行Shell命令返回状态码是0为成功。
下面是一个livenessProbe的例子。

来自官方例子

livenessProbe.yaml:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: anjia0532/busybox # 官方的镜像可能下载不下来,可以替换成这个
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

在这个配置文件中,可以看到 Pod 中只有一个容器。 periodSeconds 字段指定了 kubelet 应该每 5 秒执行一次存活探测。 initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 5 秒。 kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。 如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。

这个容器生命的前 30 秒, /tmp/healthy 文件是存在的。 所以在这最开始的 30 秒内,执行命令 cat /tmp/healthy 会返回成功代码。 30 秒之后,执行命令 cat /tmp/healthy就会返回失败代码。

创建pod:

kubectl apply -f livenessProbe.yaml

在 30 秒内,查看 Pod 的事件:

kubectl describe pod liveness-exec

输出结果表明还没有存活探测器失败:

Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  28s   default-scheduler  Successfully assigned default/liveness-exec to 192.168.1.7
  Normal  Pulling    27s   kubelet            Pulling image "anjia0532/busybox"
  Normal  Pulled     7s    kubelet            Successfully pulled image "anjia0532/busybox" in 19.559680245s
  Normal  Created    6s    kubelet            Created container liveness
  Normal  Started    6s    kubelet            Started container liveness

35 秒之后,再来看 Pod 的事件。在输出结果的最下面,有信息显示存活探测器失败了,这个容器被杀死并且被重建了。

Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  90s                default-scheduler  Successfully assigned default/liveness-exec to 192.168.1.7
  Normal   Pulling    89s                kubelet            Pulling image "anjia0532/busybox"
  Normal   Pulled     69s                kubelet            Successfully pulled image "anjia0532/busybox" in 19.559680245s
  Normal   Created    68s                kubelet            Created container liveness
  Normal   Started    68s                kubelet            Started container liveness
  Warning  Unhealthy  24s (x3 over 34s)  kubelet            Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  Normal   Killing    24s                kubelet            Container liveness failed liveness probe, will be restarted

再等另外 30 秒,检查看这个容器被重启了:

kubectl get pod liveness-exec

输出结果显示RESTARTS的值增加了1:

NAME            READY   STATUS    RESTARTS   AGE
liveness-exec   1/1     Running   1          109s

httpGet

发送HTTP请求,返回200-400范围状态码为成功
http-liveness.yaml:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: anjia0532/liveness
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

在这个配置文件中,可以看到 Pod 也只有一个容器。 periodSeconds 字段指定了 kubelet 每隔 3 秒执行一次存活探测。 initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 3 秒。 kubelet 会向容器内运行的服务(服务会监听 8080 端口)发送一个 HTTP GET 请求来执行探测。 如果服务器上/healthz 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。 如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。

任何大于或等于 200 并且小于 400 的返回代码标示成功,其它返回代码都标示失败。

tcpSocket

发起TCP Socket建立成功。

tcp-liveness-readiness.yaml

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: anjia0532/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

TCP 检测的配置和 HTTP 检测非常相似。 下面这个例子同时使用就绪和存活探测器。kubelet 会在容器启动 5 秒后发送第一个就绪探测。 这会尝试连接 goproxy 容器的 8080 端口。 如果探测成功,这个 Pod 会被标记为就绪状态,kubelet 将继续每隔 10 秒运行一次检测。

除了就绪探测,这个配置包括了一个存活探测。 kubelet 会在容器启动 15 秒后进行第一次存活探测。 就像就绪探测一样,会尝试连接 goproxy 容器的 8080 端口。 如果存活探测失败,这个容器会被重新启动。

kubectl apply -f tcp-liveness-readiness.yaml

15 秒之后,通过看 Pod 事件来检测存活探测器:

kubectl describe pod goproxy

Pod调度策略

所谓调度策略,在集群环境中,即将如何将pod分配到某个节点上。

我们先来看一下创建Pod流程。

创建Pod流程

在这里插入图片描述

  1. 用户通过kubectl 发送create pod请求给api serverapiserver生成包含创建信息的yaml,然后将yaml信息写入ectd数据库。
  2. api server触发watch机制准备创建pod,信息转发给调度器scheduler,调度器使用调度算法选择节点,调度器将节点信息给api serverapi server将绑定的节点信息写入etcd
  3. apis erver又通过watch机制,调用kubelet,指定pod信息,触发docker run命令创建容器。
  4. 创建完成之后反馈给kubelet, kubeletpod的状态信息给api server,api server又将pod的状态信息写入etcd
  5. 如果发送kubectl get pods命令,此时调用的是etcd的信息。

在创建流程的过程中,通过schedulerpod调度到某个node上,那么调度过程中会受哪些因素影响呢?

主要受下面四个方面影响:

  • pod资源限制
  • 节点选择器标签
  • 节点亲和性
  • 污点和污点容忍

下面一一来分析。

Pod资源限制

比如我们在上面见到的yaml文件中的请求(request)和限制(limit)。

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-ctr
    image: polinux/stress
    resources:
      limits:
        memory: "200Mi"
        cpu: "500m"
      requests:
        memory: "100Mi"
        cpu: "250m"

在上面的例子中,请求的是100M内存和250M的CPU。那么k8s就会根据请求找到能够符合资源需求的节点进行调度。

节点选择器

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    env_role: dev

其中nodeSelector就是标签选择器,可以约束一个Pod只能在特定的节点上运行。

那么如何为节点增加标签呢,假如就要增加上面所示节点选择器的标签,可以这样做:

kubectl label node node1 env_role=dev
kubectl label node node2 env_role=prod

这样就可以对不同的节点打上不同的标签。

nodeSelector是节点选择约束的最简单推荐形式。nodeSelectorPodspec 的一个字段。 它包含键值对的映射。为了使 Pod可以在某个节点上运行,该节点的标签中 必须包含这里的每个键值对(它也可以具有其他标签)。 最常见的用法的是一对键值对。

节点亲和性

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: env_role
            operator: In
            values:
            - dev
            - test
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: group
            operator: In
            values:
            - otherProd                   
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent

nodeAffinity是节点亲和性的标签,类似nodeSelector,根据节点上标签约束来决定Pod调度到哪些节点上。
但是节点亲和性极大地扩展了你可以表达约束的类型。关键增强包括:

  1. 语言更具有表现力(不再是简单的完全规则匹配)。
  2. 你可以发现规则是软需求/偏好,而不是硬性要求,因此,如果调度器无法满足该要求,仍然调度该Pod。
  3. 你可以使用节点上的 Pod 的标签来约束,而不是使用节点本身的标签,来允许哪些 pod 可以或者不可以被放置在一起。

目前有两种类型的节点亲和性,分别为requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution
可以看它们为硬需求软需求。前者指定将Pod调度到一个节点上必须满足的规则,后者指定调度器将尝试执行但不能保证的偏好。
名称“IgnoredDuringExecution”部分意味着,类似于 nodeSelector 的工作原理, 如果节点的标签在运行时发生变更,从而不再满足 Pod 上的亲和性规则,那么 Pod 将仍然继续在该节点上运行。

污点和容忍度

在这里插入图片描述

(图片来自于https://leoh0.github.io/post/2018-08-07-kubernetes-dedicated-node-pattern)
污点(taint)是**定义在节点之上**的键值型属性数据,用于让节点有能力主动拒绝调度器将`Pod`调度运行到节点上,除非该`Pod`对象具有接纳节点污点的容忍度(toleration)。

容忍度则是定义在Pod对象上的键值型属性数据,用于配置该Pod可容忍的节点污点。

上图的例子共有4个节点,但是前两个节点有污点position=low,第一个Pod有相应的污点容忍度,则可以调度到上面,以及剩下的无污点的节点上。
但是第二个Pod只能调度在剩下的无污点的节点上。

节点亲和性是Pod的一种属性,它使 Pod 被吸引到一类特定的节点。 这可能出于一种偏好,也可能是硬性要求。 污点(taint)则相反,它使节点能够排斥一类特定的 Pod

每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod,是不会被该节点接受的。

污点值有三个:

  • NoSchedule : 一定不被调度
  • PreferNoSchedule : 尽量不会调度
  • NoExecute : 不会调度,并且还会驱逐节点已有Pod

概念有点晦涩,我们来看一个例子。

添加污点命令格式为:

kubectl taint nodes  [node_name] [key=value] : [NoSchedule|PreferNoSchedule |NoExecute]

然后开始敲命令吧。

# 部署Pod
yjw@rancher1:~$ kubectl create deploy web --image=nginx
deployment.apps/web created
# 部署5份
yjw@rancher1:~$ kubectl scale deploy web --replicas=5
deployment.apps/web scaled
# 查看部署情况
yjw@rancher1:~$ kubectl get po -o wide
NAME                              READY   STATUS              RESTARTS   AGE     IP             NODE          NOMINATED NODE   READINESS GATES
web-96d5df5c8-52jnx               0/1     ContainerCreating   0          5m2s    <none>         192.168.1.7   <none>           <none>
web-96d5df5c8-9g6cg               1/1     Running             0          5m2s    10.42.0.11     192.168.1.6   <none>           <none>
web-96d5df5c8-gztck               0/1     ContainerCreating   0          5m2s    <none>         192.168.1.7   <none>           <none>
web-96d5df5c8-wgrd9               1/1     Running             0          5m2s    10.42.0.12     192.168.1.6   <none>           <none>
web-96d5df5c8-wqfqm               0/1     ContainerCreating   0          6m38s   <none>         192.168.1.7   <none>           <none>

可以看到,节点1.6和节点1.7都有部署Pod。这里的IP应该替换成节点名称好一点,但是现阶段k8s还不支持直接更改节点名称,所以先不折腾了。

接下来我们清除这些Pod,然后把节点1.6打上污点,让节点1.6不被调度。

# 删除deployment web
yjw@rancher1:~$ kubectl delete deploy web
deployment.apps "web" deleted
# 给节点1.6打上污点
yjw@rancher1:~$ kubectl taint nodes 192.168.1.6 env_role=yes:NoSchedule
node/192.168.1.6 tainted
# 查看节点1.6的污点
yjw@rancher1:~$ kubectl describe node 192.168.1.6 | grep Taint
Taints:             env_role=yes:NoSchedule

接下来,我们再次部署web:

yjw@rancher1:~$ kubectl create deploy web --image=nginx
deployment.apps/web created
yjw@rancher1:~$ kubectl scale deploy web --replicas=5
deployment.apps/web scaled
# 查看部署情况
yjw@rancher1:~$ kubectl get pods -o wide
NAME                              READY   STATUS              RESTARTS   AGE     IP             NODE          NOMINATED NODE   READINESS GATES
web-96d5df5c8-2hjwr               0/1     ContainerCreating   0          7m59s   <none>         192.168.1.7   <none>           <none>
web-96d5df5c8-5rjbw               0/1     ContainerCreating   0          8m29s   <none>         192.168.1.7   <none>           <none>
web-96d5df5c8-l9xdh               0/1     ContainerCreating   0          7m59s   <none>         192.168.1.7   <none>           <none>
web-96d5df5c8-m8mms           0/1     ContainerCreating   0          7m59s   <none>         192.168.1.7   <none>           <none>
web-96d5df5c8-sjhkc               0/1     ContainerCreating   0          8m      <none>         192.168.1.7   <none>           <none>

可以看到,全部部署到了节点 192.168.1.7上了,拉取镜像有点慢,我们就不等了。

那么如何删除污点呢

yjw@rancher1:~$ kubectl describe node 192.168.1.6 | grep Taint
Taints:             env_role=yes:NoSchedule
yjw@rancher1:~$ kubectl taint nodes 192.168.1.6 env_role:NoSchedule-
node/192.168.1.6 untainted
yjw@rancher1:~$ kubectl describe node 192.168.1.6 | grep Taint
Taints:             <none>

我们再看看容忍度的应用:

apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    env: test
spec:
  containers:
  - name: web
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "env_role"
    operator: "Equal"
    value: "yes"
    effect: "NoSchedule"

然后再部署看看:

yjw@rancher1:~$ kubectl taint nodes 192.168.1.6 env_role=yes:NoSchedule
node/192.168.1.6 tainted
yjw@rancher1:~/temp$ kubectl apply -f taint.yaml 
pod/web created
yjw@rancher1:~/temp$ kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE          IP             NODE          NOMINATED NODE   READINESS GATES
web                                     1/1     Running                0          28s    10.42.0.11     192.168.1.6    <none>           <none>

可以看到,已经部署到了1.6上了,哪怕它上面有一个污点,也是有可能被调度到的,就这是污点容忍的用法。

污点定义在节点的nodeSpec中,而容忍度定义在PodpodSpec中,它们都是键值型数据,但又都额外支持一个效用(effect)标识,
语法格式为key=value:effect

污点上的效用标识用于定义其对Pod对象的排斥等级,容忍度上的效用标识则用于定义其对污点的容忍级别。效用标识主要有以下三种类型:

  • NoSchedule :不能容忍此污点的Pod对象不可调度至当前节点,属于强制型约束关系,但添加污点对节点上现存的Pod对象不产生影响。
  • PreferNoScheduleNoSchedule 的柔性约束版本,即调度器尽量确保不会将那些不能容忍此污点的Pod对象调度至当前街道,除非不存在其他任何能容忍此污点的可用节点;同样添加此类效用的污点对节点上现存的Pod对象不产生影响。
  • NoExecute:不能容忍此污点的新Pod对象不可调度至当前节点,属于强制型约束关系,而且节点上现存的Pod对象因节点污点变动或Pod容忍度变动而不再满足匹配条件时,Pod对象将会被驱逐。

上例中的operator: "Equal"说的是等值比较,是在Pod对象上定义容忍度时,支持的两种操作符之一。表示容忍度与污点必须在keyvalueeffect三者之上完全匹配;另一种是存在性判断(operator: "Exists"),表示二者的keyeffect必须完全匹配,而容忍度中的value字段要使用空值。

污点的使用场景主要有:

  • 用作专用节点
  • 配备了特殊硬件的节点
  • 基于污点的驱逐

Controller

什么是Controller

Controller是管理和运行容器的对象。
K8s中内建了多种控制器,用来控制Pod的具体状态和行为。

Pod和Controller的关系

Pod是通过Controller来实现应用的运维,比如伸缩、滚动升级等。

Pod和Controller通过label标签建立关系。

Deployment控制器应用场景

DeploymentPodReplicaSet 提供了一个声明式定义(declarative)方法,用来替代以前的 ReplicationController 来方便的管理应用。
典型的应用场景包括:

  • 部署无状态应用
  • 管理PodReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续 Deployment

Deployment控制器部署应用

简单的,可以通过命令行的方式:

kubectl create deployment wb --image=nginx

但是这种方式不好复用,而且只适合于参数简单的情况,如果参数复杂且繁多时就不太适用了。

k8s还提供了通过yaml文件部署的方式。

kubectl create deployment web --image=nginx --dry-run -o yaml > web.yaml

不记得yaml的写法没关系,我们可以通过上述命令生成,然后修改成自己想要的即可。
生成的yaml如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: web
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web # selector匹配下面的标签 app: web
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: web # 标签 app: web
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
        ports:
        - containerPort: 80
status: {}

我们不修改,直接部署:

yjw@rancher1:~$ kubectl apply -f web.yaml 
deployment.apps/web created
yjw@rancher1:~$ kubectl get po
NAME                              READY   STATUS              RESTARTS   AGE
web-96d5df5c8-qxslf               1/1     Running   0          45s

现在nginx部署好了,但是还是无法通过本地浏览器访问,需要暴露端口对外发布。

# 也是生成yaml文件的方式
kubectl expose deployment web --port=80 --target-port=80 --type=NodePort --name=web-nodeport -o yaml > web-port.yaml

这个生成的就比较长,我把核心的贴一下:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: web
  name: web-nodeport
  namespace: default
spec:
  clusterIP: 10.43.45.231
  externalTrafficPolicy: Cluster
  ports:
  - nodePort: 30474
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: web
  sessionAffinity: None
  type: NodePort
status:
  loadBalancer: {}

nodePort: 30474是k8s帮我们生成的外部访问端口。

yjw@rancher1:~$ kubectl apply -f web-port.yaml 
yjw@rancher1:~$ kubectl get svc
NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes     ClusterIP   10.43.0.1       <none>        443/TCP        20d
web-nodeport   NodePort    10.43.45.231    <none>        80:30474/TCP   3m18s

这里有多个端口,大概说一下区别。

  • NodePort 提供了外部客户端访问service的一种方式,可以通过nodeIP:nodePort访问集群中的service
  • port serice暴露在cluster ip上的端口,集群内的其他Pod可以通过该端口与之通信。
  • TargetPort Pod上监听的端口,服务会通过该端口向Pod发送请求
  • containerPort (Docker)容器暴露的端口

nodePortport都是service的端口,nodePort暴露给外部客户端访问,port暴露给集群内部服务访问。经过这两个端口的数据需要经过kube-proxy,代理到podtargetPort上,最后到达Pod内容器的containerPort

在这里插入图片描述
我们可以通过该端口访问刚才部署的nginx服务。

升级回滚和弹性伸缩

我们上小节没有指定nginx版本,默认安装是最新的,为了演示。修改web.yaml文件:

    spec:
      containers:
      - image: nginx:1.14

然后再重新部署

yjw@rancher1:~$ kubectl delete pod web
pod "web" deleted
yjw@rancher1:~$ kubectl delete svc web-nodeport
service "web-nodeport" deleted
yjw@rancher1:~$ kubectl apply -f web.yaml 
deployment.apps/web configured
yjw@rancher1:~$ kubectl get po
NAME                              READY   STATUS    RESTARTS   AGE
web-88c6cbf44-cd2fd               1/1     Running   0          40s

我们需要先删除之前部署的。来验证一下部署的版本:

yjw@rancher1:~$ kubectl describe po web-88c6cbf44-cd2fd
...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  100s  default-scheduler  Successfully assigned default/web-88c6cbf44-cd2fd to 192.168.1.7
  Normal  Pulling    98s   kubelet            Pulling image "nginx:1.14"
  Normal  Pulled     73s   kubelet            Successfully pulled image "nginx:1.14" in 24.105553689s
  Normal  Created    69s   kubelet            Created container nginx
  Normal  Started    69s   kubelet            Started container nginx

下面我们来升级nginx的版本,升级为1.15。

yjw@rancher1:~$ kubectl set image deployment web nginx=nginx:1.15
deployment.apps/web image updated
yjw@rancher1:~$ kubectl get po
NAME                              READY   STATUS              RESTARTS   AGE
web-586db47859-5sd75              0/1     ContainerCreating   0          3s  # 先创建一个新的
web-88c6cbf44-cd2fd               1/1     Running             0          3m34s
yjw@rancher1:~$ kubectl get po
NAME                              READY   STATUS        RESTARTS   AGE 
web-586db47859-5sd75              1/1     Running       0          25s # 新的创建好之后
web-88c6cbf44-cd2fd               1/1     Terminating   0          3m56s # 停止之前旧的
yjw@rancher1:~$ kubectl describe po web-586db47859-5sd75
...
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  2m58s  default-scheduler  Successfully assigned default/web-586db47859-5sd75 to 192.168.1.7
  Normal  Pulling    2m57s  kubelet            Pulling image "nginx:1.15"
  Normal  Pulled     2m35s  kubelet            Successfully pulled image "nginx:1.15" in 21.507159641s
  Normal  Created    2m34s  kubelet            Created container nginx
  Normal  Started    2m34s  kubelet            Started container nginx
yjw@rancher1:~$ kubectl rollout status deployment web # 也可以这样查看升级状态
deployment "web" successfully rolled out

下面来演示一下回滚,将版本还原会到nginx1.14。

# 查看历史版本
yjw@rancher1:~$ kubectl rollout history deploy web
deployment.apps/web 
REVISION  CHANGE-CAUSE
1         <none> # 1.14
2         <none> # 1.15
# #
# 回滚有两种做法,第一种:回到上一个版本
##
yjw@rancher1:~$ kubectl rollout undo deployment web
deployment.apps/web rolled back
yjw@rancher1:~$ kubectl get po
NAME                  READY   STATUS    RESTARTS   AGE
web-88c6cbf44-7rkj2   1/1     Running   0          86s
yjw@rancher1:~$ kubectl describe po web-88c6cbf44-7rkj
...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  96s   default-scheduler  Successfully assigned default/web-88c6cbf44-7rkj2 to 192.168.1.7
  Normal  Pulling    95s   kubelet            Pulling image "nginx:1.14"
  Normal  Pulled     84s   kubelet            Successfully pulled image "nginx:1.14" in 10.666448411s
  Normal  Created    84s   kubelet            Created container nginx
  Normal  Started    84s   kubelet            Started container nginx
# #
# 回滚有两种做法,第二种:回滚到指定版本
##
yjw@rancher1:~$ kubectl rollout history deploy web
deployment.apps/web 
REVISION  CHANGE-CAUSE
1         <none>
3         <none>
4         <none>
yjw@rancher1:~$ kubectl rollout undo deploy web  --to-revision=1 # 切回到最原始的版本,即不指定nginx版本,默认最新版
deployment.apps/web rolled back
yjw@rancher1:~$ kubectl get po
NAME                  READY   STATUS        RESTARTS   AGE
web-88c6cbf44-7rkj2   0/1     Terminating   0          5m1s
web-96d5df5c8-s6bnp   1/1     Running       0          13s
yjw@rancher1:~$ kubectl get po
NAME                  READY   STATUS    RESTARTS   AGE
web-96d5df5c8-s6bnp   1/1     Running   0          14s
yjw@rancher1:~$ kubectl describe po web-96d5df5c8-s6bnp
...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  32s   default-scheduler  Successfully assigned default/web-96d5df5c8-s6bnp to 192.168.1.7
  Normal  Pulling    31s   kubelet            Pulling image "nginx"
  Normal  Pulled     27s   kubelet            Successfully pulled image "nginx" in 3.509383287s
  Normal  Created    27s   kubelet            Created container nginx
  Normal  Started    27s   kubelet            Started container nginx

下面来看下弹性伸缩,所谓弹性伸缩可以理解为根据实际情况,弹性地调整实例部署的数量。

yjw@rancher1:~$ kubectl get po
NAME                  READY   STATUS    RESTARTS   AGE
web-96d5df5c8-s6bnp   1/1     Running   0          2m26s

现在我们只部署了一个实例,假如你的应用很火,用的人很多,我们就像多部署几个,比如5个:

yjw@rancher1:~$ kubectl scale deploy web --replicas=5
deployment.apps/web scaled
yjw@rancher1:~$ kubectl get pod
NAME                  READY   STATUS              RESTARTS   AGE
web-96d5df5c8-b6tjg   0/1     ContainerCreating   0          7s
web-96d5df5c8-mn5qd   0/1     ContainerCreating   0          7s
web-96d5df5c8-nt64z   1/1     Running             0          7s
web-96d5df5c8-s6bnp   1/1     Running             0          4m39s
web-96d5df5c8-sh6pk   0/1     ContainerCreating   0          7s

部署有状态应用

有状态应用什么意思?我们先来看下无状态应用。
无状态:

  • 认为所有Pod都是一样的
  • 没有顺序要求
  • 不用考虑在哪个节点上运行
  • 可以随意进行伸缩和扩展

那有状态呢:

  • 上面的4点都要考虑
  • 让每个Pod独立,保存每个Pod启动顺序和唯一性
    • 唯一的网络标识符
    • 持久存储
    • 有序

有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 “None” 来创建无头服务。

下面我们使用StatefulSet来部署有状态应用。

sts.yaml

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: nginx-statefulset
spec:
  selector:
    matchLabels:
      app: nginx # has to match .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # by default is 1
  template:
    metadata:
      labels:
        app: nginx # has to match .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

执行

yjw@master:~/temp$ kubectl apply -f sts.yaml 
service/nginx created
statefulset.apps/nginx-statefulset created

查看创建的pod:

yjw@master:~/temp$ kubectl get po 
NAME                  READY   STATUS    RESTARTS   AGE
nginx-statefulset-0   1/1     Running   0          2m41s
nginx-statefulset-1   1/1     Running   0          2m27s
nginx-statefulset-2   1/1     Running   0          2m24s

查看无头服务:

yjw@master:~/temp$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.43.0.1    <none>        443/TCP   10d
nginx        ClusterIP   None         <none>        80/TCP    4m16s

CLUSTER-IP : None可以看出它是无头服务。

deploymentStatefulSet的区别:是否有唯一标识

StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。 组合主机名的格式为$(StatefulSet 名称)-$(序号)。 上例将会创建三个名称分别为 nginx-statefulset-0nginx-statefulset-1nginx-statefulset-2 的 Pod。 StatefulSet 可以使用无头服务 控制它的 Pod 的网络域。管理域的这个服务的格式为: $(服务名称).$(命名空间).svc.cluster.local,其中 cluster.local 是集群域。 一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为:$(pod 名称).$(所属服务的 DNS 域名),其中所属服务由 StatefulSetserviceName 域来设定。

部署守护进程

守护进程(DaemonSet) 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod

DaemonSet 的一些典型用法:

  • 在每个节点上运行集群守护进程
  • 在每个节点上运行日志收集守护进程
  • 在每个节点上运行监控守护进程

来看一个实例。
ds.yaml:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: ds-test 
  labels:
    app: filebeat
spec:
  selector:
    matchLabels:
      app: filebeat
  template:
    metadata:
      labels:
        app: filebeat
    spec:
      containers:
      - name: logs
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: varlog
          mountPath: /tmp/log
      volumes:
      - name: varlog
        hostPath:
          path: /var/log

这里假设是一个日志采集工具。

yjw@master:~/temp$ vim ds.yaml
yjw@master:~/temp$ kubectl apply -f ds.yaml
daemonset.apps/ds-test created
yjw@master:~/temp$ kubectl get po
NAME            READY   STATUS    RESTARTS   AGE
ds-test-4k6hw   1/1     Running   0          29s
ds-test-hj6d2   1/1     Running   0          29s
ds-test-xxj4n   1/1     Running   0          29s

我们可以进入某个pod查看:

# 进入pod的 bash
yjw@master:~/temp$ kubectl exec -it ds-test-4k6hw -- bash
# 查看打印的日志
root@ds-test-4k6hw:/# ls /tmp/log
alternatives.log    dist-upgrade  landscape    syslog.5.gz
alternatives.log.1  dmesg	  lastlog      syslog.6.gz
apt		    dpkg.log	  pods	       syslog.7.gz
auth.log	    dpkg.log.1	  private      sysstat
auth.log.1	    faillog	  syslog       ubuntu-advantage.log
bootstrap.log	    installer	  syslog.1     unattended-upgrades
btmp		    journal	  syslog.2.gz  wtmp
btmp.1		    kern.log	  syslog.3.gz
containers	    kern.log.1	  syslog.4.gz
# 退出pod的 bash
root@ds-test-4k6hw:/# exit
exit
yjw@master:~/temp$

部署一次性和定时任务

还可以部署一次性任务(job)和定义任务(cronjob)。

job.yaml:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4 # 失败后尝试4次

上面是一个 Job 配置示例。它负责计算 π 到小数点后 2000 位,并将结果打印出来。 此计算大约需要 10 秒钟完成。

yjw@master:~/temp$ kubectl create -f job.yaml 
job.batch/pi created
# 等待片刻
yjw@master:~/temp$ kubectl get jobs
NAME   COMPLETIONS   DURATION   AGE
pi     1/1           57s        86s

上面说明完成了,可以查看输出的内容。

yjw@master:~/temp$ kubectl logs  pi-s4zjz
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

下面来看下定时任务。
cronjob.yaml:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

上面示例会在每分钟打印出当前时间和问候消息:

yjw@master:~/temp$ kubectl apply -f cronjob.yaml 
cronjob.batch/hello created
yjw@master:~/temp$ kubectl get pod
NAME                     READY   STATUS      RESTARTS   AGE
ds-test-4k6hw            1/1     Running     0          16m
ds-test-hj6d2            1/1     Running     0          16m
ds-test-xxj4n            1/1     Running     0          16m
hello-1620308640-wm8ts   0/1     Completed   0          64s
hello-1620308700-gkt2l   0/1     Completed   0          4s
# 查看定时任务打印的日志
yjw@master:~/temp$ kubectl logs hello-1620308640-wm8ts
Thu May  6 13:44:13 UTC 2021
Hello from the Kubernetes cluster

Service

定义Pod的访问规则。

概述

Service存在的意义:
(1) 防止Pod失联(服务发现)

yjw@rancher1:~$ kubectl get po -o wide
NAME                  READY   STATUS    RESTARTS   AGE     IP             NODE          NOMINATED NODE   READINESS GATES
web-96d5df5c8-b6tjg   1/1     Running   0          3m9s    10.42.192.8    192.168.1.7   <none>           <none>
web-96d5df5c8-mn5qd   1/1     Running   0          3m9s    10.42.192.10   192.168.1.7   <none>           <none>
web-96d5df5c8-nt64z   1/1     Running   0          3m9s    10.42.192.1    192.168.1.7   <none>           <none>
web-96d5df5c8-s6bnp   1/1     Running   0          7m41s   10.42.192.3    192.168.1.7   <none>           <none>
web-96d5df5c8-sh6pk   1/1     Running   0          3m9s    10.42.192.11   192.168.1.7   <none>           <none>

如果查看更加详细的信息,可以看到每个pod都有一个自己的内部ip:10.42.*
因为Pod是短暂存在的,上面进行的滚动更新、升级回滚都会停止旧的Pod,而创建新的Pod。而每个Pod的IP都是独立的。

Service通过服务发现,保存每个Pod的IP,类似SpringCloud中的EurekaPod创建后会注册到Service中。这样通过Service就可以访问到Pod,防止Pod失联。

(2) 定义一组Pod访问策略(负载均衡)
在这里插入图片描述

假设你部署了3个实例,用于分流,Service就可以做到一些访问策略。详见虚拟 IP 和 Service 代理

PodService是怎么建立联系的呢?

在这里插入图片描述
也是通过标签选择来实现的。也是通过这种关系来实现负载均衡和服务发现。

在 k8s集群中,每个 Node 运行一个 kube-proxy 进程。 kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式。

Service类型

yjw@rancher1:~$ kubectl expose --help
...
 --type='': Type for this service: ClusterIP, NodePort, LoadBalancer, or
ExternalName. Default is 'ClusterIP'.

可以看到有四种类型。

  • ClusterIP:通过集群的内部 IP(上面说的虚拟IP) 暴露服务,选择该值时服务只能够在集群内部访问。 这也是默认的 ServiceType
  • NodePort:通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>,你可以从集群的外部访问一个 NodePort 服务。
  • LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。
  • ExternalName:通过返回 CNAME 和对应值,可以将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。 无需创建任何类型代理。

我们先清掉之前配置的内容,最终达到:

yjw@rancher1:~$ kubectl get po
No resources found in default namespace.
yjw@rancher1:~$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.43.0.1    <none>        443/TCP   20d

然后我们也先生成一个service.yaml文件:

yjw@rancher1:~$ kubectl apply -f web.yaml 
deployment.apps/web created
yjw@rancher1:~$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.43.0.1    <none>        443/TCP   20d
yjw@rancher1:~$ kubectl expose deploy web --port=80 --target-port=80 --dry-run=client -o yaml > service1.yaml
yjw@rancher1:~$ ls
service1.yaml  temp  web-port.yaml  web.yaml

service1.yaml内容如下:

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: web
  name: web
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: web
  #type: ClusterIP
status:
  loadBalancer: {}

默认没有指定type,即为ClusterIP类型。

yjw@rancher1:~$ kubectl apply -f service1.yaml 
service/web created
yjw@rancher1:~$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.43.0.1      <none>        443/TCP   20d
web          ClusterIP   10.43.95.131   <none>        80/TCP    5s

我们可以在集群内部访问这个IP:

yjw@rancher1:~$ curl 10.43.95.131
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

下面来看下NodePort

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: web
  name: web1 # 修改name,防止重复
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: web
  type: NodePort # 指定NodePort 
status:
  loadBalancer: {

应用修改

yjw@rancher1:~$ vim service1.yaml 
yjw@rancher1:~$ kubectl apply -f service1.yaml 
service/web1 created
yjw@rancher1:~$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.43.0.1      <none>        443/TCP        20d
web          ClusterIP   10.43.95.131   <none>        80/TCP         2m56s
web1         NodePort    10.43.114.51   <none>        80:31202/TCP   4s

可以看到,k8s默认给我们分配了一个端口31202,通过这个端口我们可以在集群外部,通过nodeIP:31202来访问nginx。
在这里插入图片描述
LoadBalancer需要用到公有云,比如阿里云、腾讯云等。
如果我们自己实现负载均衡,可以用一台可以访问外网的机器,安装nginx,进行反向代理。需要手动添加访问节点到nginx中。

配置管理

Secret

Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 SSH 密钥。 将这些信息放在 secret 中比放在 Pod规约中或者 容器镜像 中来说更加安全和灵活。

用户可以创建 Secret,同时系统也创建了一些 Secret

在为创建 Secret 编写配置文件时,你可以设置 datastringData 字段。 datastringData 字段都是可选的。data 字段中所有键值都必须是 base64 编码的字符串。

下面来看如何创建Secret

secret.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque # 用户定义的任意数据
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

应用:

yjw@master:~/temp$ vim secret.yaml
yjw@master:~/temp$ kubectl create -f secret.yaml 
secret/mysecret created
yjw@master:~/temp$ kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-2l7hs   kubernetes.io/service-account-token   3      10d
mysecret              Opaque 

创建好了,怎么应用呢。可以以变量形式挂载到pod容器中。

secret-var.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: nginx
    image: nginx
    env:
      - name: SECRET_USERNAME
        valueFrom: # 变量形式挂载
          secretKeyRef:
            name: mysecret # secret名称
            key: username  # secret中的key
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password

应用:

yjw@master:~/temp$ kubectl apply -f secret-var.yaml 
pod/mypod created
yjw@master:~/temp$ kubectl get po
NAME    READY   STATUS    RESTARTS   AGE
mypod   1/1     Running   0          2m13s
# 进入查看
yjw@master:~/temp$ kubectl exec -it mypod -- bash
# 打印保存的用户名和密码变量
root@mypod:/# echo $SECRET_USERNAME
admin
root@mypod:/# echo $SECRET_PASSWORD
1f2d1e2e67df

还可以以数据卷(volumes)的形式挂载到pod中。

secret-vol.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts: # 数据卷形式
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret

应用:

yjw@master:~/temp$ kubectl delete -f secret-v
secret-var.yaml  secret-vol.yaml  
yjw@master:~/temp$ kubectl delete -f secret-v
secret-var.yaml  secret-vol.yaml  
yjw@master:~/temp$ kubectl delete -f secret-var.yaml 
pod "mypod" deleted
yjw@master:~/temp$ kubectl apply -f secret-vol.yaml 
pod/mypod created
yjw@master:~/temp$ kubectl get po
NAME    READY   STATUS    RESTARTS   AGE
mypod   1/1     Running   0          34s
# 进入pod查看
yjw@master:~/temp$ kubectl exec -it mypod -- bash
# 挂载的目录
root@mypod:/# ls /etc/foo
password  username
root@mypod:/# cat /etc/foo/username 
admin

ConfigMap

ConfigMap 是一种 API 对象,和Secret不一样的是,ConfigMap用来将非机密性的数据保存到键值对中。使用时, Pods 可以将其用作环境变量、命令行参数或者存储卷中的配置文件

下面以redis配置文件为例。

假设我们redis的配置文件为:
redis.properties:

redis.host=127.0.0.1
redis.port=6379
redis.password=123456

下面来创建ConfigMap

# 从配置文件创建ConfigMap
yjw@master:~/temp$ kubectl create configmap redis-config --from-file=redis.properties
configmap/redis-config created
yjw@master:~/temp$ kubectl get cm
NAME               DATA   AGE
kube-root-ca.crt   1      10d
redis-config       1      8s
yjw@master:~/temp$ kubectl describe cm redis-config
Name:         redis-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
redis.properties:
----
redis.host=127.0.0.1
redis.port=6379
redis.password=123456


Events:  <none>
yjw@master:~/temp$ 

下面先来看下以Volume挂载到pod容器中。

cm.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: busybox
      image: busybox
      command: [ "/bin/sh","-c","cat /etc/config/redis.properties" ] # 打印配置文件中的内容
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap: # 指定configmap
        name: redis-config
  restartPolicy: Never

应用:

yjw@master:~/temp$ kubectl apply -f cm.yaml
pod/mypod created
yjw@master:~/temp$ kubectl get po
NAME    READY   STATUS      RESTARTS   AGE
mypod   0/1     Completed   0          15s
yjw@master:~/temp$ kubectl logs mypod
redis.host=127.0.0.1
redis.port=6379
redis.password=123456

ConfigMap也可以以变量的形式挂载到pod中。

首先声明变量。
myconfig.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: myconfig
  namespace: default
data:
  special.level: info
  special.type: hello

应用之前先删掉上面的:

yjw@master:~/temp$ kubectl delete -f cm.yaml
pod "mypod" deleted
yjw@master:~/temp$ vim myconfig.yaml
yjw@master:~/temp$ kubectl apply -f myconfig.yaml 
configmap/myconfig created
yjw@master:~/temp$ kubectl get cm
NAME               DATA   AGE
kube-root-ca.crt   1      11d
myconfig           2      7s
redis-config       1      14m

下面以变量的形式挂载。

config-var.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: busybox
      image: busybox
      command: [ "/bin/sh", "-c", "echo $(LEVEL) $(TYPE)" ] # 打印这两个变量
      env:
        - name: LEVEL #变量名
          valueFrom:
            configMapKeyRef:
              name: myconfig
              key: special.level # 变量的key
        - name: TYPE
          valueFrom:
            configMapKeyRef:
              name: myconfig
              key: special.type
  restartPolicy: Never

应用:

yjw@master:~/temp$ vim config-var.yaml
yjw@master:~/temp$ kubectl apply -f config-var.yaml 
pod/mypod created
yjw@master:~/temp$ kubectl get po
NAME    READY   STATUS      RESTARTS   AGE
mypod   0/1     Completed   0          4s
yjw@master:~/temp$ kubectl logs mypod
info hello

集群安全机制

当我们访问k8s集群时,需要经过三个步骤才能完成具体步骤。
在这里插入图片描述

  1. 认证(Authentication)
    • 运行一个或多个身份认证组件,将请求验证为来自特定的用户
  2. 鉴权(Authorization)
    • 请求必须包含请求者的用户名、请求的行为以及受该操作影响的对象。 如果现有策略声明用户有权完成请求的操作,那么该请求被鉴权通过。可以基于RBAC进行鉴权操作。
  3. 准入控制(Admission control)
    • 准入控制可以修改或拒绝请求。准入控制模块还可以访问正在创建或修改的对象的内容。

进行访问时,都需要经过apiserver做同一协调。访问过程中需要证书、token或用户名+密码。

除了这些,还有一个重要的概念是传输安全(Transport security)。
API 服务器在 443 端口上提供服务,受 TLS 保护。即https证书认证。

RBAC介绍

基于角色(Role)的访问控制(RBAC)是一种基于组织中用户的角色来调节控制对计算机或网络资源的访问的方法。

在这里插入图片描述

上面是RBAC的一个例子,用户lucy通过角色绑定(rolebinding,rb)获得了对资源的一些权限。其中涉及到的概念有很多,下面一一解释。

  • 角色(role):设置特定的命名空间访问权限
  • 集群角色(ClusterRole):集群所有命名空间的访问权限
  • 角色绑定(RoleBinding):将角色绑定到主体上
  • 集群角色绑定(ClusterRoleBinding):将集群角色绑定到主体
  • 用户(user)
  • 用户组(group)
  • 服务账号(ServiceAccount)

Ingress

我们之前暴露服务是通过NodePort暴露端口号,然后通过ip+端口号进行访问。
但是这种方法有缺陷:

  • 会在每个节点上开启端口,即每个端口只能使用一次
  • 而且实际访问是通过域名访问,根据不同域名路由跳转到不同的端口服务中

而引入Ingress就是为了解决上面的问题。那么Ingress是如何访问到pod的呢?

Ingress作为统一入口,由Service关联一组pod
在这里插入图片描述
上面的路由规则是基于域名www.xx.com,通过前缀URL/foo/路由到某个服务。

下面我们来进行实操加深印象。

应用Ingress

①创建nginx应用,使用NodePort对外暴露端口

yjw@master:~/temp$ kubectl create deployment web --image=nginx
deployment.apps/web created
yjw@master:~/temp$ kubectl get po
NAME                  READY   STATUS    RESTARTS   AGE
web-96d5df5c8-992bf   1/1     Running   0          8s
yjw@master:~/temp$ kubectl expose deployment web --port=80 --target-port=80 --type=NodePort
service/web exposed
yjw@master:~/temp$ kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.43.0.1     <none>        443/TCP        13d
web          NodePort    10.43.42.44   <none>        80:31169/TCP   11s

此时,我们可以直接通过主机IP+31169 进行访问:
在这里插入图片描述
②部署Ingress Controller

yjw@master:~/temp$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/baremetal/deploy.yaml
namespace/ingress-nginx configured
serviceaccount/ingress-nginx created
configmap/ingress-nginx-controller created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
service/ingress-nginx-controller-admission created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
serviceaccount/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created

③创建Ingress规则

创建tls secret

yjw@master:~/temp/certs$ kubectl create secret tls tls-ingress-www-greyfoss --cert=cert1.pem --key=privkey1.pem
secret/tls-ingress-www-greyfoss  created

证书是通过 let’s encrypt免费获取的

ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: www.greyfoss.top
    http:
      paths:
      - path: /web
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 80
  tls:
  - hosts:
    -  www.greyfoss.top
    secretName: tls-ingress-www-greyfoss
yjw@master:~/temp$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/minimal-ingress created
yjw@master:~/temp$ kubectl describe ing minimal-ingress
Name:             minimal-ingress
Namespace:        default
Address:
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
  tls-ingress-www-greyfoss terminates www.greyfoss.top
Rules:
  Host              Path  Backends
  ----              ----  --------
  www.greyfoss.top
                    /web   web:80 (10.42.128.2:80)
Annotations:        nginx.ingress.kubernetes.io/rewrite-target: /
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  Sync    23s   nginx-ingress-controller  Scheduled for sync
  Normal  Sync    23s   nginx-ingress-controller  Scheduled for sync
  Normal  Sync    23s   nginx-ingress-controller  Scheduled for sync
  Normal  Sync    23s   nginx-ingress-controller  Scheduled for sync

在这里插入图片描述
由于我们添加了真实的证书,显示连接是安全的。

这样我们就通过前缀/web路由到了添加的nginx服务。

helm

在上面我们介绍的内容中,如果我们要部署应用,首先需要编写yaml文件;然后需要对外暴露端口;然后部署Ingress暴露应用。

那么这种方式有什么缺点呢?这种方式适用于部署单一应用、少数服务的应用。

如果部署微服务项目,假设有几十个服务,每个服务都有一套自己的yaml文件,那么维护起来将是个灾难。

引入heml就是为了解决这些问题,那可以解决哪些问题?

  • 使用helm可以把这些yaml文件作为一个整体管理
  • 实现yaml文件高效复用
  • 使用helm实现应用级别的版本管理

概述

Helm是一个k8s的包管理工具,就像Linux下的yum/apt等,可以很方便的将之前打包好的yaml文件部署到k8s上。

Helm有3个重要概念:

  1. helm:一个命令行客户端工具,主要用于k8s应用chart的创建、打包、发布和管理
  2. Chart:应用描述,yaml集合,一系列用于描述k8s资源相关文件的集合
  3. Release:基于Chart的部署实体,一个chartHelm运行后将会生成对应的一个release;将在k8s中创建出真实运行的资源对象

当前稳定版本Helm v3新特性:

  • v3版本移除Tiller
  • release支持在不同命名空间中重用
  • 支持将chart推送到docker镜像仓库

在这里插入图片描述
helm的架构如上,下面我们实际来使用一下helm

安装和配置仓库

安装helm:

# 下载安装包
yjw@master:~/temp$ wget wget http://rancher-mirror.cnrancher.com/helm/v3.5.3/helm-v3.5.3-linux-amd64.tar.gz
...
Downloaded: 1 files, 12M in 3.9s (3.02 MB/s)
# 解压
yjw@master:~/temp$  tar -zxvf helm-v3.5.3-linux-amd64.tar.gz
linux-amd64/
linux-amd64/helm
linux-amd64/LICENSE
linux-amd64/README.md
# 移动到/usr/bin目录
yjw@master:~/temp$ sudo  mv linux-amd64/helm /usr/bin/helm
# 添加可执行
yjw@master:~/temp$ chmod +x /usr/bin/helm

配置仓库:

# 添加国内仓库
helm repo add stable http://mirror.azure.cn/kubernetes/charts/
helm repo add aliyun  https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts/
# 查看添加的仓库
helm repo list

输出

yjw@master:~$ helm repo list
NAME          	URL                                                    
stable        	http://mirror.azure.cn/kubernetes/charts/              
aliyun        	https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts/

如果想删除:

# 删除
yjw@master:~$ helm repo remove aliyun
"aliyun" has been removed from your repositories
# 更新
yjw@master:~$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
# 查看最新
yjw@master:~$ helm repo list
NAME          	URL                                              
stable        	http://mirror.azure.cn/kubernetes/charts/  

快速部署应用

接下来我们用helm来部署应用。这里以应用weave为例。

① 搜索应用

yjw@master:~$ helm search repo weave
NAME              	CHART VERSION	APP VERSION	DESCRIPTION                                       
stable/weave-cloud	0.3.9        	1.4.0      	DEPRECATED - Weave Cloud is a add-on to Kuberne...
stable/weave-scope	1.1.12       	1.12.0     	DEPRECATED - A Helm chart for the Weave Scope c...

②根据搜索内容选择安装

yjw@master:~$ helm install ui stable/weave-scope
WARNING: This chart is deprecated
W0510 13:17:19.397944 2491757 warnings.go:70] rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRole
W0510 13:17:19.404973 2491757 warnings.go:70] rbac.authorization.k8s.io/v1beta1 ClusterRoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRoleBinding
W0510 13:17:19.541409 2491757 warnings.go:70] rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRole
W0510 13:17:19.576348 2491757 warnings.go:70] rbac.authorization.k8s.io/v1beta1 ClusterRoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRoleBinding
NAME: ui
LAST DEPLOYED: Mon May 10 13:17:18 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
You should now be able to access the Scope frontend in your web browser, by
using kubectl port-forward:

kubectl -n default port-forward $(kubectl -n default get endpoints \
ui-weave-scope -o jsonpath='{.subsets[0].addresses[0].targetRef.name}') 8080:4040

then browsing to http://localhost:8080/.
For more details on using Weave Scope, see the Weave Scope documentation:

https://www.weave.works/docs/scope/latest/introducing/

③查看安装之后的状态

yjw@master:~$ helm status ui
NAME: ui
LAST DEPLOYED: Mon May 10 13:17:18 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
You should now be able to access the Scope frontend in your web browser, by
using kubectl port-forward:

kubectl -n default port-forward $(kubectl -n default get endpoints \
ui-weave-scope -o jsonpath='{.subsets[0].addresses[0].targetRef.name}') 8080:4040

then browsing to http://localhost:8080/.
For more details on using Weave Scope, see the Weave Scope documentation:

https://www.weave.works/docs/scope/latest/introducing/

yjw@master:~$ kubectl get po
NAME                                            READY   STATUS    RESTARTS   AGE
weave-scope-agent-ui-4zb64                      1/1     Running   0          6m24s
weave-scope-agent-ui-bgxv6                      1/1     Running   0          6m24s
weave-scope-agent-ui-v92sr                      1/1     Running   0          6m24s
weave-scope-cluster-agent-ui-5cbc84db49-xf665   1/1     Running   0          6m24s
weave-scope-frontend-ui-6698fd5545-d6lxs        1/1     Running   0          6m24s
web-96d5df5c8-fmns2                             1/1     Running   0          6h7m
yjw@master:~$ kubectl get svc
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.43.0.1       <none>        443/TCP        7h10m
ui-weave-scope   ClusterIP   10.43.253.226   <none>        80/TCP         6m45s
web              NodePort    10.43.96.109    <none>        80:32303/TCP   6h7m

自定义chart部署

上面我们安装应用的过程中,并没有自己编写yaml文件,是不是很方便。

yaml文件实际上是通过chart来部署的,本节来探讨如何自定义chart

①创建chart

yjw@master:~$ helm create mychart
Creating mychart
yjw@master:~$ cd mychart/
yjw@master:~/mychart$ ls
Chart.yaml  charts  templates  values.yaml

该命令会创建同名的文件夹,里面有chart模板。

yjw@master:~/mychart$ ll
...
-rw-r--r-- 1 yjw yjw  349 May 10 13:28 .helmignore
-rw-r--r-- 1 yjw yjw 1143 May 10 13:28 Chart.yaml # 当前chart属性配置信息
drwxr-xr-x 2 yjw yjw 4096 May 10 13:28 charts/
drwxr-xr-x 2 yjw yjw 4096 May 10 13:32 templates/ # 存放yaml文件集合
-rw-r--r-- 1 yjw yjw 1899 May 10 13:28 values.yaml # 定义yaml文件中可以使用的全局变量

②在templates文件夹中创建两个yaml文件

# 先创建deployment.yaml
yjw@master:~/mychart/templates$ kubectl create deploy web1 --image=nginx --dry-run=client -o yaml > deployment.yaml
# 真正部署web1,以方便生成service.yaml
yjw@master:~/mychart/templates$ kubectl create deploy web1 --image=nginx
deployment.apps/web1 created
# 创建service.yaml
yjw@master:~/mychart/templates$ kubectl expose deployment web1 --port=80 --target-port=80 --type=NodePort --dry-run=client -o yaml > service.yaml
# 删除deployment web1
yjw@master:~/mychart/templates$ kubectl delete deploy web1
deployment.apps "web1" deleted

这样,我们两个yaml文件就创建好了。

③安装mychar:

yjw@master:~/mychart$ cd ~
yjw@master:~$ helm install web1 mychart
NAME: web1
LAST DEPLOYED: Mon May 10 13:41:38 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

yjw@master:~$ kubectl get pods
NAME                                            READY   STATUS    RESTARTS   AGE
weave-scope-agent-ui-4zb64                      1/1     Running   0          25m
weave-scope-agent-ui-bgxv6                      1/1     Running   0          25m
weave-scope-agent-ui-v92sr                      1/1     Running   0          25m
weave-scope-cluster-agent-ui-5cbc84db49-xf665   1/1     Running   0          25m
weave-scope-frontend-ui-6698fd5545-d6lxs        1/1     Running   0          25m
web-96d5df5c8-fmns2                             1/1     Running   0          6h26m
web1-6fbb48567f-wp87b                           1/1     Running   0          43s
yjw@master:~$ kubectl get svc
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.43.0.1       <none>        443/TCP        7h28m
ui-weave-scope   ClusterIP   10.43.253.226   <none>        80/TCP         25m
web              NodePort    10.43.96.109    <none>        80:32303/TCP   6h25m
web1             NodePort    10.43.251.126   <none>        80:32326/TCP   54s

可以看到我们自定义的web1部署成功了。

④应用升级

yjw@master:~$ helm upgrade web1 mychart
Release "web1" has been upgraded. Happy Helming!
NAME: web1
LAST DEPLOYED: Mon May 10 13:43:46 2021
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None

chart模板使用

我们可以通过helm实现yaml文件高效复用:

  • 通过传递参数,动态渲染模板,yaml内容根据参数动态生成

我们需要修改mychart目录下的values.yaml文件,定义全局变量。

本例中yaml文件中大体上有几个地方是可变的:

  • image
  • tag
  • label
  • port
  • replicas

①在values.yaml中定义变量和值
values.yaml:

replicas : 1
image: nginx
tag: 1.16
label: nginx
port: 80

②在具体yaml中,获取定义的变量值

通过表达式形式使用变量

  • {{.Values.变量名称}}
  • {{.Release.Name}} : 获取当前版本名称

修改deployment.yaml为:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: {{ .Values.label}}
  name: {{ .Release.Name}}-deploy
spec:
  replicas: {{ .Values.replicas}}
  selector:
    matchLabels:
      app: {{ .Values.label}}
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: {{ .Values.label}}
    spec:
      containers:
      - image: {{ .Values.image}}
        name: nginx
        resources: {}
status: {}

修改service.yaml为:

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: {{ .Values.label}}
  name: {{ .Release.Name}}-svc
spec:
  ports:
  - port: {{ .Values.port}}
    protocol: TCP
    targetPort: 80
  selector:
    app: {{ .Values.label}}
  type: NodePort
status:
  loadBalancer: {}

下面我们用dry-run看一下效果:

yjw@master:~$ helm install --dry-run web2 mychart
NAME: web2
LAST DEPLOYED: Mon May 10 14:08:02 2021
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
HOOKS:
MANIFEST:
---
# Source: mychart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: web2-svc
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: NodePort
status:
  loadBalancer: {}
---
# Source: mychart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: web2-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

可以看到yaml文件修改成功,下面我们真正执行一下:

yjw@master:~$ helm install web2 mychart
NAME: web2
LAST DEPLOYED: Mon May 10 14:10:06 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
yjw@master:~$ kubectl get po
NAME                                            READY   STATUS    RESTARTS   AGE
weave-scope-agent-ui-4zb64                      1/1     Running   0          52m
weave-scope-agent-ui-bgxv6                      1/1     Running   0          52m
weave-scope-agent-ui-v92sr                      1/1     Running   0          52m
weave-scope-cluster-agent-ui-5cbc84db49-xf665   1/1     Running   0          52m
weave-scope-frontend-ui-6698fd5545-d6lxs        1/1     Running   0          52m
web-96d5df5c8-fmns2                             1/1     Running   0          6h54m
web1-6fbb48567f-wp87b                           1/1     Running   0          28m
web2-deploy-6799fc88d8-x2gcl                    1/1     Running   0          6s
yjw@master:~$ kubectl get svc
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.43.0.1       <none>        443/TCP        7h56m
ui-weave-scope   ClusterIP   10.43.253.226   <none>        80/TCP         52m
web              NodePort    10.43.96.109    <none>        80:32303/TCP   6h53m
web1             NodePort    10.43.251.126   <none>        80:32326/TCP   28m
web2-svc         NodePort    10.43.130.17    <none>        80:30073/TCP   11s

可以看到web2部署成功,这样就实现了yaml文件的高效复用。

持久化存储

持久化存储,即当pod重启后,数据依然存在。

主要有两种方式:nfs网络存储和pv/pvc

nfs网络存储

①找一台服务器,专门做nfs服务端,设置挂载路径
博主找到了另外一台CentOS系统:

[root@instance-54lh4cfv graves]# yum install -y nfs-utils

/etc/exports:

/data/nfs *(rw,no_root_squash)

设置挂载路径:

[root@instance-54lh4cfv graves]# vim /etc/exports
[root@instance-54lh4cfv graves]# cat /etc/exports
/data/nfs *(rw,no_root_squash)
[root@instance-54lh4cfv graves]# mkdir /data/nfs

②在k8s集群节点上安装nfs
在集群所有节点都执行以下命令

sudo apt install nfs-kernel-server

③在nfs服务端启动nfs服务
在CentOS系统上执行:

[root@instance-54lh4cfv ~]# systemctl start nfs
[root@instance-54lh4cfv ~]# ps -ef | grep nfs
root     16612     2  0 22:52 ?        00:00:00 [nfsd4_callbacks]
root     16618     2  0 22:52 ?        00:00:00 [nfsd]
root     16619     2  0 22:52 ?        00:00:00 [nfsd]
root     16620     2  0 22:52 ?        00:00:00 [nfsd]
root     16621     2  0 22:52 ?        00:00:00 [nfsd]
root     16622     2  0 22:52 ?        00:00:00 [nfsd]
root     16623     2  0 22:52 ?        00:00:00 [nfsd]
root     16624     2  0 22:52 ?        00:00:00 [nfsd]
root     16625     2  0 22:52 ?        00:00:00 [nfsd]
root     16636 16525  0 22:52 pts/1    00:00:00 grep --color=auto nfs

在master节点上执行新建文件夹,保存以下内容到nfs-nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-dep1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: wwwroot
          mountPath: /usr/share/nginx/html # 将此路径内容挂载到nfs服务器 /data/nfs上
        ports:
        - containerPort: 80
      volumes:
        - name: wwwroot
          nfs:
            server: 106.12.109.x # CentOS服务器地址
            path: /data/nfs

应用:

yjw@master:~/pv$ kubectl apply -f nfs-nginx.yaml 
deployment.apps/nginx-dep1 created
yjw@master:~/pv$ kubectl get po
NAME                                            READY   STATUS    RESTARTS   AGE
nginx-dep1-58664cb68b-cw7qb                     1/1     Running   0          12s
...
# 进入该pod
yjw@master:~/pv$ kubectl exec -it nginx-dep1-58664cb68b-cw7qb -- bash
# 目前该目录为空
root@nginx-dep1-58664cb68b-cw7qb:/# ls /usr/share/nginx/html/

然后我们在CentOS对应的目录下新建一个文件:

[root@instance-54lh4cfv nfs]# vim index.html
[root@instance-54lh4cfv nfs]# cat index.html 
hello from nfs
[root@instance-54lh4cfv nfs]# 

再切换回master查看:

root@nginx-dep1-58664cb68b-cw7qb:/# ls /usr/share/nginx/html/
index.html
root@nginx-dep1-58664cb68b-cw7qb:/# cat /usr/share/nginx/html/index.html 
hello from nfs

此时文件已经同步过来了,相当于文件可以持久化到nfs服务器上。

pv/pvc

本节我们介绍另一种方案,涉及到两个概念:pv和pvc。

  • pv (PersistentVolume) : 持久化存储,对存储资源进抽象,对外提供可以调用的接口(生产者)
  • pvc (PersistentVolumeClaim):用于调度,不需要关心内部实现细节(消费者)

实现流程如下:
在这里插入图片描述
下面来实操一下。

pvc.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-dep1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: wwwroot
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
      volumes:
      - name: wwwroot
        persistentVolumeClaim: # pvc
          claimName: my-pvc

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteMany # 匹配模式
  resources:
    requests:
      storage: 5Gi # 容量

pvc不关心实现细节,由pv来具体实现。这里我们还是通过nfs。

pv.yaml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /k8s/nfs
    server: 192.168.44.134

应用:

# 部署pvc
yjw@master:~/temp$ kubectl apply -f pvc.yaml 
deployment.apps/nginx-dep1 configured
persistentvolumeclaim/my-pvc created
# 部署pv
yjw@master:~/temp$ kubectl apply -f pv.yaml 
persistentvolume/my-pv created
# 查看部署情况
yjw@master:~/temp$ kubectl get pv,pvc
NAME                     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
persistentvolume/my-pv   5Gi        RWX            Retain           Bound    default/my-pvc                           27s

NAME                           STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/my-pvc   Bound    my-pv    5Gi        RWX                           2m58s
# 查看Pod情况
yjw@master:~/temp$ kubectl get po
NAME                                            READY   STATUS    RESTARTS   AGE
nginx-dep1-69f5bb95b-dqlfs                      1/1     Running   0          29s
nginx-dep1-69f5bb95b-ftjjk                      1/1     Running   0          37s
nginx-dep1-69f5bb95b-z47s5                      1/1     Running   0          3m24s
...
# 进入pod
yjw@master:~/temp$ kubectl exec -it nginx-dep1-69f5bb95b-dqlfs -- bash
# 查看目录
root@nginx-dep1-69f5bb95b-dqlfs:/# ls /usr/share/nginx/html/  
index.html
root@nginx-dep1-69f5bb95b-dqlfs:/# 

参考

  1. Kubernetes免费视频教程
  2. K8s官网文档
  3. Kubernetes Handbook——Kubernetes 中文指南/云原生应用架构实践手册
  4. Kubernetes进阶实战(第2版)
Logo

开源、云原生的融合云平台

更多推荐