Kubernetes 基础教程


在之前的文章中我们分享了 DockerDocker Compose 的使用,前者通过容器化技术简化了服务管理,而后者则提供 YAML 方式进一步简化 Docker 维护成本。

而今天则让我们聚焦于 K8S(Kubernetes),其提供了容器化编排能力,在规模化服务运维上提供了强力保障。

随着 Kubernetes 的生态的发展,其已然成为当下服务运维的主流选择,那么今天让我们一同学习如何基于 K8S 实现服务的部署与管理。

一、集群搭建

1. 环境配置

开始前需先行配置相关的环境,通过下面命令关闭 swap,完成后可通过 free -m 验证。

swapoff -a
sed -i '/swap/d' /etc/fstab

同时,若在测试环境则建议先行关闭服务器防火墙避免后续访问异常。

systemctl stop firewalld
systemctl disable firewalld

2. Minikube

在后续的教程中都是基于 Docker 实现,因此部署 Kubernetes 前需确保 Docker 已安装并运行,可参考:Docker基础入门

完成后便可着手部署本地 Kubernetes 服务,这里选择 Minikube 搭建服务集群。

CentOS7 服务器为例,访问 GitHub 下载 minikube-linux-amd64 二进制文件并上传至服务器,或使用 curl 命令在服务器上通过网络下载:

curl -LO https://github.com/kubernetes/minikube/releases/latest/download/minikube-linux-amd64

下载完成后则可进行安装,完成后输入 minikube 若显示 help 命令说明安装成功。

install minikube-linux-amd64 /usr/local/bin/minikube

服务安装后便可启动 Kubernetes 服务,其中 --driver=docker 表示基于 Docker 服务。

同时 minikube 默认不支持 root 账户运行,因此需 --force 强制启动进而跳过校验。

minikube start --driver=docker --force

若需以普通用户身份启动,可通过下述命令新建用户后启动。

# 创建用户并加入组
useradd budai
usermod -aG docker budai

# 切换用户并启动
su budai
minikube start --driver=docker

完成上述步骤后通过 minikube status 查看服务状态,返回下述结果则说明成功。

[root@localhost ~]# minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

3. 命令关联

为了更方便后续的使用,这边需要为 kubectl 命令创建短链别名。

通过 vim .bashrc 编辑环境变量,在配置中加入 alias kubectl="minikube kubectl --" 后保存退出。

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias kubectl="minikube kubectl --"

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

保存退出后通过 source 命令刷新配置,便可直接使用 kubectl 命令。

source .bashrc

二、POD管理

1. 基础介绍

Docker 服务中我们最熟悉的莫过于 container 容器了,而在 KubernetesPod 为最小的可调度、可运行单元,是一个或多个容器的组合。

当启动 Pod 时会为其分配单独 IP,不同 Pod 之间可通过此分配的 IP 进行通讯访问。

较为经典的场景即日志监控,将日志服务与业务系统部署于同一 Pod 中,当系统输出日志信息后由日志服务推送数据至其它 Pod 用于进一步加工处理。

2. 配置格式

Kubernetes 中服务的管理与 Docker Compose 类似通过 YAML 文件进行管理。

下面让我们来看一个具体的 Pod 实例其 test-pod.yaml 文件配置,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  labels:
    app: test-label
spec:
  restartPolicy: Always
  containers:
    - name: k8s-test
      image: k8s-test:1.0
      imagePullPolicy: IfNotPresent

对于上述 yaml 各项参数作用参考下表,详细说明可访问官网查看:Pod

属性 作用
kind 配置类型。
metadata.name 用于指定 Pod 名称。
metadata.labels 用于指定 Pod 标签。
spec.restartPolicy Pod 重启策略。
spec.containers 用于配置 Pod 中运行的容器信息。

其中标签 (labels) 最核心的作用便是用于资源选择,让其它服务能够选中 Pod,后续会以具体示例演示。

同时,containers 容器中配置的镜像拉取默认规则为读取 Docker Hub,通过 imagePullPolicy 便可指定拉取策略,可选值如下:

属性 作用
Always 默认值,总是拉取镜像。
IfNotPresent 当本地不存在时拉取镜像。
Never 只使用本地镜像,从不拉取。

除此之外,Pod 其余常见命令参考下表:

命令 作用
kubectl get pod 列举当前所有 Pod。
kubectl get pod -o wide 列举当前所有 Pod 详细信息。
kubectl get pod --show-labels 列举当前所有 Pod 及其 label。
kubectl delete pod pod-name 删除指定 Pod。
kubectl logs --follow pod-name 查看指定 Pod 日志。

同时与 Docker 中的 exec 类似,Pod 也支持类似语法访问 Pod,其语法如下:

kubectl exec -it <pod-name> -- <command>

如进入 test-pod 节点内部其相对应的命令如下:

kubectl exec -it test-pod -- /bin/bash

3. 服务示例

接下来以具体的 Pod 演示,准备 Spring Boot 项目定义接口返回当前节点的 Host name,代码如下:

@RestController
@RequestMapping("/api")
public TestResource {

    @GetMapping("hello")
    public String hello() throws Exception {
        InetAddress inet = InetAddress.getLocalHost();
        return inet.getHostName() + " say Hello!"
    }
}

完成后将工程打包为 Docker Image,可参考:Maven打包部署教程,制作好镜像后通过 load 命令加载至 Minikube 中。

# 构建镜像
docker build -t k8s-test:1.0 .

# 加载镜像
minikube image load k8s-test:1.0

# 列举镜像
minikube image list

查看 minikube 镜像时除了上述命令也可通过 minikube ssh 进入实例后使用普通 docker 命令查看。

镜像加载完毕后便可通过 kubectl apply -f 命令启动上述 yaml 定义的 Pod 服务。

# 启动 Pod 实例
kubectl apply -f test-pod.yaml  

此时便可通过 kubectl get pod 查看已有的 Pod,如下 Running 则表示以启动运行。

[root@localhost ~]# kubectl get pod
NAME       READY   STATUS    RESTARTS   AGE
test-pod   1/1     Running   0          1s

使用 -o wide 参数可查看 Pod 详细信息,通过 IP 测试之前服务声明的接口可以看到能够正常响应。

至此,我们便完成了最基础的 Kubernetes 服务的启动运行。

[root@localhost ~]# kubectl get pod -o wide
NAME       READY   STATUS    RESTARTS   AGE     IP           NODE       NOMINATED NODE   READINESS GATES
test-pod   1/1     Running   0          1s      10.244.0.4   minikube   <none>           <none>

[root@localhost ~]# kubectl exec -it test-pod -- curl http://10.244.0.4:9090/api/hello
test-pod say Hello!

4. 重启策略

不同于手动方式的服务部署,Kubernetes 支持监控服务状态并实现异常自启。

Pod 中的容器退出时,Kubernetes 会以指数级回退延迟机制(10 秒、20 秒、40 秒…)重启容器, 上限为 300 秒。当容器顺利执行了 10 分钟,Kubernetes 就会重置该容器的重启延迟计时器。

对应 Pod 实例的重启策略配置通过 restartPolicy 配置,可选参数如下:

属性 作用
Always 默认值,只要容器终止就自动重启。
OnFailure 只有在容器错误退出(退出状态非零)时才重新启动容器。
Never 不会自动重启已终止的容器。

需注意这里重启策略是针对实例中容器而言,即当 Pod 内进程异常时重启 Pod 服务,而手动的删除 Pod 并不会触发自动重新拉取并启动 Pod

下面以示例进行介绍,首先通过 -watch 来监听 Pod 的变化情况。

kubectl get pods -watch

新建命令窗口通过 exec 查看服务进程,并使用 kill <pid> 命令关闭进程。

# 查看进程
[root@localhost app]# kubectl exec -it test-pod -- ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1 15.6  2.9 3167616 122228 ?      Ssl  12:23   0:03 java -jar /k8s-test.jar
root          30  0.0  0.0  10864  2476 pts/0    Rs+  12:24   0:00 ps aux

# 关闭进程
[root@localhost app]# kubectl exec -it test-pod -- kill 1

回到刚才的 watch 窗口,可以看到在首次启动后状态为 Running,而当手动 kill 转为 Error 后便自动触发重启,服务再次变为 Running 可用。

[root@localhost app]# kubectl get pods -w
NAME       READY   STATUS    RESTARTS   AGE
test-pod   0/1     Pending   0          0s
test-pod   0/1     Pending   0          0s
test-pod   0/1     ContainerCreating   0          0s
test-pod   1/1     Running             0          1s
test-pod   0/1     Error               0          41s
test-pod   1/1     Running             1 (1s ago)   42s

5. 目录挂载

熟悉 Docker 的都知道,容器的每次删除重建并不会保留其中的数据文件,故通常会采用目录挂载的方式实现数据的持久化。

Pod 中也并不例外,每次执行 delete pod 后其容器中的所有内载文件将一并删除无法找回。

因此,部署服务时对于重要数据同样需配置目录映射,在 Docker 中若需实现容器与宿主机的目录映射通过 -v 参数便可实现,而在 Pod 中配置方式与其相似。

还是以刚刚的 test-pod.yaml 文件为例,若需要将宿主机的 /home/data 目录与容器目录 /app/data 相映射,其配置如下:

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  labels:
    app: test-label
spec:
  restartPolicy: Always
  containers:
    - name: k8s-test
      image: k8s-test:1.0
      imagePullPolicy: IfNotPresent
      # 容器目录
      volumeMounts:
        - name: host-data
          mountPath: /app/data
    # 宿主机目录
    volumes:
        - name: host-data
          hostPath:
            path: /home/data
            type: DirectoryOrCreate

其中 volumes 用于定义宿主机目录信息,而 volumeMounts 相对应为容器目录信息,二者通过 volumes.namevolumeMounts.name 实现关联。

同时,由于我们是基于 Minikube 所以这边需要增加一步操作将宿主机与 Minikube 进行映射:

minikube mount <宿主机目录>:<Minikube 目录>

通过上述的配置即可实现 Pod 的目录映射,但你也发现的问题所在即强依赖于服务节点,在集群模式下的 Kubernetes 不同节点可能分步在不同服务器上,映射目录并不互通。

因此对于集群目录映射提供了 PersistentVolumeClaim 持久化配置方式,将数据独立存储管理,如下配置中声明了空间大小为 5GB 的持久化目录。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

而后同样通过 volumesvolumeMounts 即可实现目录映射,此时即便删除 test-pod 节点,数据仍会持久化保存于 my-pvc 当中。

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  labels:
    app: test-label
spec:
  restartPolicy: Always
  containers:
    - name: k8s-test
      image: k8s-test:1.0
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - name: pers-data
          mountPath: /app/data
    volumes:
        - name: pers-data
          persistentVolumeClaim:
            claimName: my-pvc

三、Deployment

1. 基础介绍

在上述的介绍中,我们成功启动了单个 Pod 实例,但在真正的应用场景,常伴随着大批量的集群化服务。

上述的方式虽然达到效果,但显然效率过于低下,因此 Kubernetes 中提供了 Deployment 提供高度集成的集群化 Pod 服务管理。

同样的,以具体的 test-deployment.yaml 配置示例进行介绍说明,文件内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-label
  template:
    metadata:
      labels:
        app: test-label
    spec:
      containers:
        - name: k8s-test
          image: k8s-test:1.0
          imagePullPolicy: IfNotPresent

配置中 kind 指定为 Deployment,配置说明参考下表,详细说明可访问官网:Deployment

方法 作用
replicas 集群副本实例数量。
selector.matchLabels.app 指定被当前 Deployment 管理的 Pod。
template.metadata.labels.app 当前 Deployment 创建 Pod 时设置的标签。

其中容器信息 containers 配置项与先前并无差异,这里着重说明 label 的配置项。

正如最开始提到的,label 用于 selector 的资源选择,当 K8S 中运行多个 Pod 实例时,selector.matchLabels.app 用于标识哪些 Pod 受当前 Deployment 的管理。而 template.metadata.labels.app 则作用于 Deployment 创建 Pod 时为这些 Pod 指定标签。

通常情况下,同一份 Deployment 配置文件下 selector.matchLabels.apptemplate.metadata.labels.app 值需保持一致。

2. 副本集群

在高可用的模式下,部署常采用分布式的形式存在,通过多实例保证在部分宕机的下服务仍保持可用。

在传统的服务部署模式下,需要手动启动多个实例服务,通过 Nginx 等代理工具实现负载均衡。

Deployment 则可通过 replicas 简化多个实例启动这一步骤,由 Kubernetes 自动拉起多个节点。

还是以示例进行演示,让我们先删除之前创建的 Pod 实例。

[root@localhost app]# kubectl get pod
NAME       READY   STATUS    RESTARTS   AGE
test-pod   1/1     Running   0          6d22h

[root@localhost app]# kubectl delete pod test-pod
pod "test-pod" deleted from default namespace

完成后修改上述的配置文件的中 replicas = 3 后与之前一样通过 apply -f 启动 Deployment

启动后可通过 get deployment 查看对应实例信息,同时 get pod 也可以看到 3Pod 实例也已启动。

通过 Deploymentreplicas 在集群化服务下我们不再需要手动逐一创建实例,通过配置便可一键启动。同时,kubelet 将监控 Pod 运行情况,当 Pod 不足 replicas 数量时会自动启动新的 Pod 实例。

# 启动 Deployment
[root@localhost app]# kubectl apply -f test-deployment.yaml 
deployment.apps/test-deployment created

# 查看 Deployment
[root@localhost app]# kubectl get deployment
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
test-deployment   3/3     3            3           3s

# 查看 Pod
[root@localhost app]# kubectl get pod -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
test-deployment-6ccd7f9d44-8nkzg   1/1     Running   0          7s    10.244.0.10   minikube   <none>           <none>
test-deployment-6ccd7f9d44-nnkch   1/1     Running   0          7s    10.244.0.12   minikube   <none>           <none>
test-deployment-6ccd7f9d44-pb2gb   1/1     Running   0          7s    10.244.0.11   minikube   <none>           <none>

例如手动删除 8nkzg 后缀的 Pod 实例后可以看到 Deployment 又自动启动了 pgxdg 后缀的实例。

如此一来,即便出现某个 Pod 宕机下线,Kubernetes 也能够立即发现且自动拉起服务,配合刚刚提到的重启策略 (restartPolicy),二者搭配便可实现服务集群的高可用。

[root@localhost app]# kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
test-deployment-6ccd7f9d44-8nkzg   1/1     Running   0          6m27s
test-deployment-6ccd7f9d44-nnkch   1/1     Running   0          6m27s
test-deployment-6ccd7f9d44-pb2gb   1/1     Running   0          6m27s

# 手动删除 Pod
[root@localhost app]# kubectl delete pod test-deployment-6ccd7f9d44-8nkzg
pod "test-deployment-6ccd7f9d44-8nkzg" deleted from default namespace

[root@localhost app]# kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
test-deployment-6ccd7f9d44-nnkch   1/1     Running   0          6m41s
test-deployment-6ccd7f9d44-pb2gb   1/1     Running   0          6m41s
test-deployment-6ccd7f9d44-pgxdg   1/1     Running   0          3s

3. 滚动升级

基于 replicas 的多副本模式下,Kubernetes 提供滚动升级的能力。即多个实例节点并不会采取一次性下线重启,避免造成短期内的服务不可用,而是分阶段的服务替换升级。

还是以刚才的 test-deployment.yaml 为例,加入 spec.strategy 控制服务升级策略。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  replicas: 3
  strategy:
     rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  selector:
    matchLabels:
      app: test-label
  template:
    metadata:
      labels:
        app: test-label
    spec:
      containers:
        - name: k8s-test
          image: k8s-test:1.0
          imagePullPolicy: IfNotPresent

Deployment 中针对 strategy 分别提供了下述两类配置策略:

  • Recreate:在启动新的 Pod 节点前删除所有旧 Pod 实例,期间服务将不可用。
  • RollingUpdate:分步删除与新增 Pod 节点,保证整个过程仍有可用服务。

启动 Recreate 相对较好理解,也是服务升级中最常用的方式,这里着重介绍滚动升级 (RollingUpdate)

从上面的 yaml 配置中可以看到其提供了两个参数,更详细的介绍可参考官网:deployment-strategy

方法 作用
maxSurge 最大峰值,指定可以的超出 replicas 的 Pod 数。
maxUnavailable 最大不可用,指定更新过程中不可用的 Pod 的上限。

以上述的配置为例,replicas = 3maxSurge = 1,即表示整个升级过程最多允许创建 4 (replicas + maxSurge)Pod,同时仅允许 1 (maxUnavailable)Pod 节点不可用。

整个服务升级过程如下,其中白色表示旧版本,灰色表示服务启动中不可用,黄色标识表示启动完成。

同时,在 Deployment 中当每次修改 spec.template 中的配置都将触发 Pod 更新。因此,在针对服务的部署更新中,我们只需重新打包镜像后修改 yamlcontainers.image 为新版后执行 apply -f 便可。

如此一来,在版本迭代发布时便无需手动繁杂的针对多个 Pod 手动进行更新发布,极大降低的工作量。

4. 服务回滚

使用 Deployment 的另一好处在于对每一次发布更新都会生成相应记录,默认下记录最近 10 次发布历史,Kubernetes 也基于此提供服务回滚的能力。

仍以刚才的工程为例,将其打包为 k8s-test:2.0 版本镜像,并修改 test-deployment.yaml 为内容为 image: k8s-test:2.0 后执行 apply -f 启动。

通过 -w 查看 Pod 的变化也恰好与刚才预期的滚动更新策略匹配,其按照之前预期的分节点停服重启。

[root@localhost app]# kubectl get pods -w
NAME                               READY   STATUS    RESTARTS   AGE
test-deployment-6ccd7f9d44-m5php   1/1     Running   0          13s
test-deployment-6ccd7f9d44-qpx2v   1/1     Running   0          13s
test-deployment-6ccd7f9d44-sn88q   1/1     Running   0          13s

test-deployment-7bc4f88555-k256h   0/1     Pending   0          0s
test-deployment-7bc4f88555-k256h   0/1     Pending   0          0s
test-deployment-6ccd7f9d44-sn88q   1/1     Terminating   0          4m33s
test-deployment-6ccd7f9d44-sn88q   1/1     Terminating   0          4m33s
test-deployment-7bc4f88555-dpp96   0/1     Pending       0          0s
test-deployment-7bc4f88555-k256h   0/1     ContainerCreating   0          0s
test-deployment-7bc4f88555-dpp96   0/1     Pending             0          0s
test-deployment-7bc4f88555-dpp96   0/1     ContainerCreating   0          0s
test-deployment-6ccd7f9d44-sn88q   0/1     Error               0          4m33s
test-deployment-7bc4f88555-k256h   1/1     Running             0          1s
test-deployment-7bc4f88555-dpp96   1/1     Running             0          1s
test-deployment-6ccd7f9d44-qpx2v   1/1     Terminating         0          4m34s
test-deployment-6ccd7f9d44-sn88q   0/1     Error               0          4m34s
test-deployment-6ccd7f9d44-sn88q   0/1     Error               0          4m34s
test-deployment-7bc4f88555-cq6q2   0/1     Pending             0          0s
test-deployment-7bc4f88555-cq6q2   0/1     Pending             0          0s
test-deployment-6ccd7f9d44-qpx2v   1/1     Terminating         0          4m34s
test-deployment-7bc4f88555-cq6q2   0/1     ContainerCreating   0          0s
test-deployment-6ccd7f9d44-m5php   1/1     Terminating         0          4m34s
test-deployment-6ccd7f9d44-m5php   1/1     Terminating         0          4m34s
test-deployment-6ccd7f9d44-qpx2v   0/1     Error               0          4m34s
test-deployment-6ccd7f9d44-m5php   0/1     Error               0          4m35s
test-deployment-7bc4f88555-cq6q2   1/1     Running             0          1s
test-deployment-6ccd7f9d44-m5php   0/1     Error               0          4m35s
test-deployment-6ccd7f9d44-m5php   0/1     Error               0          4m35s
test-deployment-6ccd7f9d44-qpx2v   0/1     Error               0          4m35s
test-deployment-6ccd7f9d44-qpx2v   0/1     Error               0          4m35s

完成更新升级之后,利用 rollout status 便可看到本次发布服务升级已经成功。

[root@localhost app]# kubectl rollout status deployment test-deployment
deployment "test-deployment" successfully rolled out

若需查看对应 Deployment 所有发布记录则可通过 rollout history 参数查看。

在下述中可以看到 test-deployment 一共经历了两次发布,首次创建以及刚刚的滚动升级。

[root@localhost app]# kubectl rollout history deployment test-deployment
deployment.apps/test-deployment 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

假设此时发现服务出现异常需回滚至上一版本,则可通过 rollout undo 回滚至指定版本。

例如下示例指定 --to-revision=1 回滚至最原始版本,查看 history 也可看到生成了对应的升级记录。

# 回滚服务
[root@localhost app]# kubectl rollout undo deployment test-deployment --to-revision=1
deployment.apps/test-deployment rolled back

# 查看历史
[root@localhost app]# kubectl rollout history deployment test-deployment
deployment.apps/test-deployment 
REVISION  CHANGE-CAUSE
2         <none>
3         <none>

四、容器探针

在上述的 Deployment 我们实现了服务的部署更新,而 K8S 其强大之处在于通过容器探针提供了服务启停与运行时的监控能力。

所谓容器探针,即在更新及运行过程中实时监控容器运行状态,根据探针结果执行一系列策略如自动重启等,下面就让我们一起了解其提供了哪些探针服务及其相应的作用。

1. 就绪探针

Kubernetes 所提供的探针服务,接下来让我们先来看就绪探针 (readinessProbe) 其对应的作用。

故名思意其用于检测更新发布过程中服务是否已经可用,在多副本 (replicas) 的负载均衡场景下,只有当就绪探针返回成功才会将流量路由至该 Pod 节点。

首先在之前的 Spring Boot 工程中新增健康检测接口,在生产环境中可使用 Actuator 健康检测。

@RestController
@RequestMapping("/api")
public TestResource {

    @GetMapping("health")
    public String health() {
        return "success"
    }
}

修改之前的 test-deployment.yaml 文件,通过 readinessProbe 配置就绪探针策略。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-label
  template:
    metadata:
      labels:
        app: test-label
    spec:
      containers:
        - name: k8s-test
          image: k8s-test:1.0
          imagePullPolicy: IfNotPresent
          readinessProbe:
            httpGet:
              path: /api/health
              port: 9090
            initialDelaySeconds: 10
            periodSeconds: 30
            timeoutSeconds: 10
            successThreshold: 3
            failureThreshold: 3
            terminationGracePeriodSeconds: 30

探针配置的类型除了 httpGet 接口外,还支持 tcpSocketgrpcexec 等形式,各项效果类型这里不展开介绍,详细配置可阅览官网教程:Container Probes

针对探针的其余参数这里列举几项常见配置,详细内容同样参考官网:Probes Config

参数 作用
initialDelaySeconds 启动后要等待多少秒后才启动探针。
periodSeconds 探测的间隔(单位是秒),默认是 10 秒,最小值是 1。
timeoutSeconds 超时后等待多少秒,默认值是 1 秒,最小值是 1。
successThreshold 指定探针连续成功多少次才算真的成功,默认值是 1。
failureThreshold 探针在失败次数阈值,默认值为 3,最小值为 1。
terminationGracePeriodSeconds 被终止时给容器优雅退出的最长等待时间(秒),默认 30 秒。

以下述就绪探针配置为例,即在服务启动 10s (initialDelaySeconds) 后启动,每隔 5s (periodSeconds) 进行重试,当响应超过 10s 则为失败。

若连续 3次 (successThreshold) 成功则表明服务可用,若超过 3次 (failureThreshold) 失败则将触发服务重启,此时向容器发起重启指定并等待 30s (terminationGracePeriodSeconds) ,若此时未能重启成功将执行服务强制重启。

readinessProbe:
    initialDelaySeconds: 10
    periodSeconds: 5
    timeoutSeconds: 10
    successThreshold: 3
    failureThreshold: 3
    terminationGracePeriodSeconds: 30

2. 存活探针

存活探针与就绪探针类似,前者作用于服务运行期间,而后者则生效于服务启动期间。

存活探针通过 livenessProbe 参数进行配置,其配置方式与上述就绪探针一致这里不在重复介绍。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-label
  template:
    metadata:
      labels:
        app: test-label
    spec:
      containers:
        - name: k8s-test
          image: k8s-test:1.0
          imagePullPolicy: IfNotPresent
          livenessProbe:
            httpGet:
              path: /api/health
              port: 9090
            initialDelaySeconds: 3
            periodSeconds: 3

3. 启动探针

启动探针 (startupProbe) 顾名思义用于指示容器中的应用是否已经启动,若配置了启动探针则所有其他探针都会被禁用,直到此探针成功为止。

如果启动探测失败,kubelet 将杀死容器,而容器依其重启策略进行重启,若容器没有提供启动探测,则默认状态为 Success

在实际的服务部署中,相对上述提到的两类探针服务,启动探针通常并不是我们关心的重点,其使用频率相对前二者也更低。

同时,其使用与就绪探针和存活探针并无差异,故这里不再介绍,详情可参考官网:StartupProbe

五、Service

1. 服务模式

之前的示例利用 Deployment 启动了多个副本,那对于这多个 Pod 实例 Kubectl 又如何实现流量管理呢?

答案就是 Service,通过 Service 以及探针 Kubectl 便可实现高精度的流量管理,以更通俗的话来讲,Service 就是 Kubectl 集群内部的 Nginx 管理服务。

针对不同的服务方式,Service 提供了下述三类资源类型,其中 ClusterIPNodePort 后面会通过具体示例进行介绍,而 LoadBalancer 则为利用各大云服务厂商的负载均衡器对外提供服务,这里便不过多阐述。

类型 描述
ClusterIP 默认,通过集群方式暴露服务。
NodePort 使用 K8S 集群节点对外暴露服务。
LoadBalancer 使用云提供商的负载均衡器向外部暴露服务。

2. ClusterIP

ClusterIP 即传统的集群服务模式,在集群内通过分配虚拟 IP 管理流量,注意这个虚拟 IP 只能被 Pod / Node 内部访问而无法对外提供服务。

让我们来看由具体的场景作为切入点,设想在 Kubernetes 集群中部署了两个系统,而各自系统都实现了多副本实例,那么两个系统间存在交互那如何通讯使流量能够均匀分步到每个节点便成了关键。

在这类场景中,可以理解 ClusterIP 为集群内部的负载均衡器,其模糊了系统内部运行细节而将其作为一个整体与集群内其它系统实现服务通讯。

同样的,让我们以来看具体的配置示例,以 ClusterIP 为例新建 test-svc-cluster.yaml 文件如下:

apiVersion: v1
kind: Service
metadata:
  name: test-svc-cluster
spec:
  type: ClusterIP
  selector:
    app: test-label
  ports:
    - port: 9090
      targetPort: 9090

其中 kind: Service 指定服务类型为 service,与之前 Deployment 类似这里通过 spec.selector.app 同样用于配置标签,标识哪些 Pod 被当前 Service 所管理。

targetPort 属性用于将 Pod 实例内的端口 port 进行映射暴露。

配置 描述
metadata.name 指定 Service 名称。
spec.type 指定 Service 类型,可选:ClusterIP,NodePort,LoadBalancer。
spec.selector.app 指定 Service 所管理的标签组。
spec.ports.port 指定 Pod 内的服务端口。
spec.ports.targetPort 指定 Service 暴露的服务端口。

启动 Service 之后可以看到 Kubernetes 为其分配了虚拟 IP = 10.104.110.236

# 启动 service
[root@localhost app]# kubectl apply -f test-svc.yaml 
service/test-svc created

[root@localhost app]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    9d
test-svc     ClusterIP   10.104.110.236   <none>        9090/TCP   12s

同时通过 get endpoints 命令查看可以看到其成功通过 label 关联之前创建的多个 Pod 节点,在 ENDPOINTS 所列的服务信息也恰好与所创建的 3Pod 实例 IP 相匹配。

[root@localhost app]# kubectl get endpoints
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME         ENDPOINTS                                            AGE
kubernetes   192.168.49.2:8443                                    9d
test-svc     10.244.0.23:9090,10.244.0.24:9090,10.244.0.25:9090   24s

[root@localhost app]# kubectl get pod -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
test-deployment-6ccd7f9d44-2chzq   1/1     Running   0          47h   10.244.0.24   minikube   <none>           <none>
test-deployment-6ccd7f9d44-d7sr7   1/1     Running   0          47h   10.244.0.25   minikube   <none>           <none>
test-deployment-6ccd7f9d44-r8xb7   1/1     Running   0          47h   10.244.0.23   minikube   <none>           <none>

让我们进入 Minikube VM 进行验证,多次请求同一个分配的虚拟 IP 可以看到返回了不同节点的 Hostname,说明 Service 成功实现负载均衡将流量覆盖到集群中的所有节点。

[root@localhost app]# minikube ssh
docker@minikube:~$ curl http://10.104.110.236:9090/api/hello
test-deployment-6ccd7f9d44-2chzq say Hello!

docker@minikube:~$ curl http://10.104.110.236:9090/api/hello
test-deployment-6ccd7f9d44-d7sr7 say Hello!

docker@minikube:~$ curl http://10.104.110.236:9090/api/hello
test-deployment-6ccd7f9d44-r8xb7 say Hello!

3. NodePort

正如上述所说的 ClusterIP 所分配的虚拟 IP 并不提供对外服务能力,但在开发调试中又常存在需临时访问服务的诉求,而 NodePort 正是为了解决这一痛点。

在刚刚的示例中通过 replicas 多副本实现高性能与高可用,倘若 Kubernetes 服务出现宕机,其内部服务不论再多实例都不起作用,因此实际生产环境中 K8S 同样是以多节点集群的形式存在。

NodePort 即为 K8S 集群中的每个节点 (Node) 上开一个端口以对外提供访问,K8S 会默认分配端口:30000–32767,访问任意 Node IP + NodePort 都能访问服务。

老规矩还是来看具体的示例,新建 test-svc-np.yaml 配置如下:

apiVersion: v1
kind: Service
metadata:
  name: test-svc-np
spec:
  type: ClusterIP
  selector:
    app: test-label
  ports:
    - port: 9090
      nodePort: 30000

NodePort 的绝大部分配置与上述 ClusterIP 并无差异,其中 ports.port 指定内网服务端口,而 ports.nodePort 则用于指定对外暴露的端口,需注意这个值可选范围在 30000–32767,若未指定则会随机分配。

如上述配置中即将内网 9090 服务暴露至 30000 端口,此时便可通过 NodeIP + NodePort 的方式访问服务。

可以看到此时通过 Node IP 便可访问服务,为临时的服务测试访问提供了通道。

# 启动服务
[root@localhost app]# kubectl apply -f test-svc-np.yaml
service/test-svc-np configured

[root@localhost app]# kubectl get svc
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes    ClusterIP   10.96.0.1        <none>        443/TCP          12d
test-svc-np   NodePort    10.97.183.23     <none>        9090:30000/TCP   2m2s

# 获取 IP
[root@localhost app]# minikube service test-svc-np --url
http://192.168.49.2:30000

# 访问服务
[root@localhost app]# curl http://192.168.49.2:30000/api/hello
test-deployment-6ccd7f9d44-d7sr7 say Hello!

由于 Minikube 与真实宿主机仍然存在一层网络隔离,所以上述的请求都是基于 Minikube VM 服务环境下,若在服务测试时需要将端口临时暴露给宿主机,则可通过 kubectl port-forward 命令。

如下便将集群内部的 9090 端口临时映射暴露至宿主机 9091 端口,但需注意 port-forward 只是调试隧道并不保证负载均衡能力,建议仅可作用临时调试使用。

kubectl port-forward svc/test-svc-np 9091:9090 --address 0.0.0.0

六、Ingress

1. 网关服务

在上述的 Service 中通过 ClusterIP 实现了集群内部的流量管理,那又如何实现与外部的通讯交互?同时,集群内多个服务拥有多个 Service 时又该如何高效的进行管理也成了一大问题。

而这一切的问题都指向同一个答案,那就是:Ingress

需要注意的是,Ingress 本身并不提供与集群外部的直接访问功能,而是提供了集群内与外部的路由网关服务。其通过协议配置的机制来解析 URI、主机名称、路径等 Web 等,是统一的 HTTP/HTTPS 流量入口与路由规则层。

2. 配置规则

同样来看具体配置示例,新建 test-ingress.yaml 文件如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
    - http:
        paths:
          - path: /api/hello
            pathType: Prefix
            backend:
              service:
                name: test-svc-cluster
                port:
                  number: 9090

Ingress 核心配置项说明如下,若需更详细内容可参阅官网文档:Ingress

参数 作用
metadata.name 指定 Ingress 名称。
metadata.annotations 指定 Ingress 配置指令。
http.paths.path 指定网关路由路径。
http.paths.pathType 路径配置类型,可选:Exact,Prefix,ImplementationSpecific。
http.paths.backend.service 路由所匹配的后端 service 服务。

其中 annotations 用于 Ingress 控制器配置行为,参考所选择的不同 Ingress 控制器查询其相应文档了解具体配置,如 minikube 默认使用 Nginx-Ingress

pathType 相对较好理解即路径匹配规则,如最常用的 ExactPrefix 分别代表严格匹配和前缀匹配。

backend 配置项则用于指定当路由规则匹配时的流量转发,如上示例将在匹配 /api/hello 时将流量转发至 test-svc-clusterservice 服务。

3. 服务示例

那么就让我们运行上述的配置示例来查看其效果,注意在 minikube 集群下要使用 Ingress 需要先启动 Ingress-Controller 服务。

minikube addons enable ingress

通过 apply -f 启动后可以看到服务正常并监听 80 端口。

[root@localhost ~]# kubectl apply -f test-ingress.yaml
# ingress.extensions/test-ingress created

[root@localhost ~]# kubectl get ingress
NAME           CLASS   HOSTS   ADDRESS   PORTS   AGE
test-ingress   nginx   *                 80      10s

正如刚刚提到 Ingress 并不提供服务访问能力而是作为网关路由,所以要访问需要借助 NodePort 实现。

刚才提到了 minikube 默认使用 Nginx-Ingress,其在对应命名下空间下提供了默认 NodePort 服务 ingress-nginx-controller

通过 --url 便可获取其对应的访问地址,其中第一个是 32666 对应 80 端口即 HTTP 服务,第二个相对应的 HTTPS 服务。

[root@localhost ~]# kubectl get svc -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.106.147.226   <none>        80:32666/TCP,443:30206/TCP   1h
ingress-nginx-controller-admission   ClusterIP   10.106.174.196   <none>        443/TCP                      1h

[root@localhost ~]# minikube service ingress-nginx-controller -n ingress-nginx --url
http://192.168.49.2:32666
http://192.168.49.2:30206

获取服务访问信息之后,便可通过此 IP 请求服务,可以看到成功实现负载均衡请求。

[root@localhost ~]# curl http://192.168.49.2:32666/api/hello
test-deployment-75ff6944d7-kfjcd say Hello!

[root@localhost ~]# curl http://192.168.49.2:32666/api/hello
test-deployment-75ff6944d7-zllwl say Hello!

[root@localhost ~]# curl http://192.168.49.2:32666/api/hello
test-deployment-75ff6944d7-vqx95 say Hello!

4. 优势差异

看到这你或许有个疑问,说到底这不是还是通过 NodePort 访问服务吗?那跟直接 Service 又有什么区别?

下面让我们以具体的场景进行介绍 Ingress 其核心竞争力到底在哪?

假设当前集群存在 3 个不同的系统集群 Service-1Service-2Service-3,通过 NodePort 方式对外提供服务,即存在 9091:300019092:300029093:30003,示意图如下:

通过图示可以看到服务器需要对外暴露三个端口,与之相对应防火墙我们也许配置三套规则规则。而在企业环境中服务数量往往及其庞大,那么其管理成本也将陡升。

那么 Ingress 其优势显而易见,将上述的场景通过 Ingress 改造后图示如下,集群只需对外暴露一个端口即可,提高安全性的同时维护成本也大大降低。

即便抛开这一切不谈,Ingress 所提供的 SSL/TLS 与域名服务解析等等能力也是 Service 所不具备。

七、命名空间

1. 基础介绍

和所有的应用一样,通常在一套集群下我们会部署多个环境服务,因此环境隔壁也在服务部署中也必不可少。

而在 Kubernetes 中一样提供了命名空间 namespace 用于实现环境隔离。

默认下所有的操作都在 default 命名空间下,通过 get namespace 可以查看当前所有命名空间。

[root@localhost app]# kubectl get namespace
NAME                   STATUS   AGE
default                Active   9d
ingress-nginx          Active   9m39s
kube-node-lease        Active   9d
kube-public            Active   9d
kube-system            Active   9d
kubernetes-dashboard   Active   3d7h

namespace 的创建相对之前可谓十分简单,新建 namespace-dev.yaml 文件如下:

apiVersion: v1
kind: Namespace
metadata:
  name: dev

完成后同样通过 apply -f 创建,而 namespace 的使用也并不复杂,在命令中通过 -n 指定便可。

例如下述命令默认查询 default 下的所有 pod 节点:

[root@localhost app]# kubectl get pod 

若需要查询刚刚创建的 dev 命名空间通过 -n dev 即可:

[root@localhost app]# kubectl get pod -n dev

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录