目录

Kubernetes | Kubernetes 集群部署

1. Kubernetes 部署本质

部署的本质,其实就是将 Kubernetes 各个组件编译成二进制文件,并且为这些二进制文件编写对应的配置文件、配置自启动脚本,以及为 kube-apiserver 配置授权文件等等诸多运维工作。(可继续参考张磊老师:k8s 的本质一节的内容)

kubernetes 安装用到的二进制

  • kubeadm
    • kubeadm.yaml
  • kubectl
  • kubelet
    • kubelet.service(如在 /etc/systemd/system/ 中)
    • kubelet.service.d/10-kubeadm.conf (如在 /etc/systemd/system/ 中)
    • /var/lib/kubelet/config.yaml

kubernetes 所需的相关组件

  • etcd(相关组件)
  • api-server
  • controller
  • scheduler
  • DNS

网络相关-CNI 插件

  • bridge 等

containerd 容器运行时

  • containerd、container-shim、runc 等二进制
  • /etc/containerd/config.toml
  • containerd.service(如:/etc/systemd/system/containerd.service)
  • /var/run/containerd/containerd.sock
  • /var/lib/containerd

命令行工具

  • crictl
    • crictl.yaml

Docker 容器运行时

  • dockerd、containerd、containerd-shim 等二进制
  • /etc/systemd/system/docker.service
  • /var/run/docker.sock、/var/run/dockershim.sock
  • /var/lib/docker

2. Kubernetes 部署发展历程

Kubernetes 项目发布初期,Kubernetes 的部署完全依靠一堆由社区维护的脚本。

2018 年的时候,各大云厂商最常用的部署方法,是使用 SaltStack、Ansible 等运维工具自动化地执行上述部署过程。但是,由于 SaltStack 这类专业的运维工具本身的学习成本,就可能比 Kubernetes 要高,所以部署过程还是相当繁琐。

SaltStack 这样的运维工具或者由社区维护的脚本的功能,就是把各个组件编译成的二进制传输到指定的机器当中,然后编写控制脚本来启停这些组件。

而为了解决 Kubernetes 部署繁琐的问题,2017 年,在志愿者的推动下,社区发起了一个独立的部署工具:kubeadm:https://github.com/kubernetes/kubeadm。kubeadm 项目的目的就是要让用户能够通过下面这两条指令完成一个 kubernetes 集群的部署:

1
2
3
4
5
# 创建一个Master节点
$ kubeadm init

# 将一个Node节点加入到当前集群中
$ kubeadm join <Master节点的IP和端口>

3. kubeadm 部署

3.1. kubeadm init 的流程

kubelet 以二进制的方式直接运行在宿主机上,然后使用容器部署其他的 kubernetes 组件。

  • 所以第一步是在机器上手动安装 kubeadm、kubelet、kubectl 这几个二进制文件。

    1
    
    apt-get install kubeadm
    
  • 接下去就可以使用 kubeadm init 来部署 master 节点了

    • kubeadm init 执行之后,首先要做的,是一系列的检查工作,以确定这台机器可以用来部署 Kubernetes,称为“Preflight Checks”。主要包含以下几个方面:

      • Linux 内核的版本必须是否是 3.10 以上
      • Linux Cgroups 模块是否可用
      • 机器的 hostname 是否标准?在 Kubernetes 项目里,机器的名字以及一切存储在 Etcd 中的 API 对象,都必须使用标准的 DNS 命名(RFC 1123)
      • 用户安装的 kubeadm 和 kubelet 的版本是否匹配
      • 机器上是不是已经安装了 Kubernetes 的二进制文件
      • Kubernetes 的工作端口 10250/10251/10252 端口是不是已经被占用
      • ip、mount 等 Linux 指令是否存在
      • Docker 是否已经安装
      • ……
    • 通过 preflight checks 之后,kubeadm 要做的就是生成 kubernetes 对外提供服务所需要的各种证书和对应的目录

      • Kubernetes 对外提供服务时,除非专门开启“不安全模式”,否则都要通过 HTTPS 才能访问 kube-apiserver。这就需要为 Kubernetes 集群配置好证书文件。kubeadm 为 Kubernetes 项目生成的证书文件都放在 Master 节点的 /etc/kubernetes/pki 目录下。在这个目录下,最主要的证书文件是 ca.crt 和对应的私钥 ca.key
      • 此外,用户使用 kubectl 获取容器日志等 streaming 操作时,需要通过 kube-apiserver 向 kubelet 发起请求,这个连接也必须是安全的。kubeadm 为这一步生成的是 apiserver-kubelet-client.crt 文件,对应的私钥是 apiserver-kubelet-client.key。
      • 除此之外,Kubernetes 集群中还有 Aggregate APIServer 等特性,也需要用到专门的证书,具体可以看相应的目录。

      需要注意的是,可以选择不让 kubeadm 为你生成这些证书,而是拷贝现有的证书到如下证书的目录里:/etc/kubernetes/pki/ca.{crt,key},这个时候 kubeadm 就会跳过证书生成的步骤,把它完全交给用户处理。

    • 证书生成后,kubeadm 接下来会为其他组件生成访问 kube-apiserver 所需的配置文件。这些文件的路径是:/etc/kubernetes/xxx.conf:

      1
      2
      
      ls /etc/kubernetes/
      admin.conf  controller-manager.conf  kubelet.conf  scheduler.conf
      

      这些文件里面记录的是,当前这个 Master 节点的服务器地址、监听端口、证书目录等信息。这样,对应的客户端(比如 scheduler,kubelet 等),可以直接加载相应的文件,使用里面的信息与 kube-apiserver 建立安全连接。

    • kubeadm 会为 Master 上所需要的组件(kube-apiserver、kube-controller-manager、kube-scheduler)生成 Pod YAML 文件。假如没有提供一个外部的 eted 服务的话,也会再生成一个 Etcd 的 Pod YAML 文件。由于这个时候 kubernetes 集群还没有起来,此时将采用 static pod 方式来启动这些 Pod。Static Pod 是把要部署的 Pod 的 YAML 文件放在一个指定的目录里,那么当这台机器上的 kubelet 启动时,它会自动检查这个目录,加载所有的 Pod YAML 文件,然后在这台机器上启动它们。

      在 kubeadm 中,Master 组件的 YAML 文件会被生成在 /etc/kubernetes/manifests 路径下,一旦相应的 YAML 文件出现在这个目录下,kubelet 就会自动创建这些 YAML 文件中定义的 Pod,即 Master 组件的容器。最后 Master 组件的 Pod YAML 文件如下所示:

      1
      2
      
      $ ls /etc/kubernetes/manifests/
      etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml
      

      kubelet 在 Kubernetes 项目中的地位非常高,在设计上它就是一个完全独立的组件,而其他 Master 组件,则更像是辅助性的系统容器。

    • 对控制平面节点应用标签和污点标记,以便不会在它上面运行其他的工作负载。

    • Master 容器启动后,kubeadm 会通过检查 localhost:6443/healthz 这个 Master 组件的健康检查 URL,等待 Master 组件完全运行起来。

    • 然后,kubeadm 就会为集群生成一个 bootstrap token。在后面,只要持有这个 token,任何一个安装了 kubelet 和 kubadm 的节点,都可以通过 kubeadm join 加入到这个集群当中。这个 token 的值和使用方法,会在 kubeadm init 结束后被打印出来。

      在 token 生成之后,kubeadm 会将 ca.crt 等 Master 节点的重要信息,通过 ConfigMap 的方式保存在 Etcd 当中,供后续部署 Node 节点使用。这个 ConfigMap 的名字是 cluster-info。

    • kubeadm init 的最后一步,就是安装默认插件。Kubernetes 默认 kube-proxy 和 DNS 这两个插件是必须安装的。它们分别用来提供整个集群的服务发现和 DNS 功能。其实,这两个插件也只是两个容器镜像而已,所以 kubeadm 只要用 Kubernetes 客户端创建两个 Pod 就可以了。

https://kubernetes.io/zh/docs/reference/setup-tools/kubeadm/kubeadm-init/

3.2. kubeadm join 的流程

kubeadm init 生成 bootstrap token 之后,你就可以在任意一台安装了 kubelet 和 kubeadm 的机器上执行 kubeadm join 了。

需要 bootstrap token 的原因

任何一台机器想要成为 kubernetes 集群中的一个节点,就必须在集群的 kube-apiserver 上注册。可是,要想跟 apiserver 打交道,这台机器就必须要获取到相应的证书文件(CA 文件)。可是,为了能够一键安装,我们就不能让用户去 Master 节点上手动拷贝这些文件。

保存在 ConfigMap 中的 cluster info。cluster info 保存了 APIServer 的授权信息,比如 kube-apiserver 的地址、端口、证书等,有了这些信息之后,。

所以这个时候 kubeadm 至少需要向 kube-apiserver 发起一次 “不安全模式”的访问,从而拿到保存在 ConfigMap 中的 cluster info,进而拿到相关的证书。而 bootstrap token,扮演的就是这个过程中的安全验证的角色,从而确保这次不安全的访问是安全的。

得到了 kube-apiserver 的地址、端口、证书之后,kubelet 就可以以安全模式连接到 apiserver 上。这样,一个新的节点就部署完成了。

3.3. 配置 kubeadm 的部署参数

默认情况下,我们使用 kubeadm initkubeadm join 的默认参数就可以完成一个集群的搭建了,但是有时候我们希望可以配置集群组件,比如 apiserver 的启动参数。这个时候也可以指定使用一个 YAML 来部署集群,

1
kubeadm init --config kubeadm.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
api:
  advertiseAddress: 192.168.0.102
  bindPort: 6443
  ...
etcd:
  local:
    dataDir: /var/lib/etcd
    image: ""
imageRepository: k8s.gcr.io
kubeProxy:
  config:
    bindAddress: 0.0.0.0
    ...
kubeletConfiguration:
  baseConfig:
    address: 0.0.0.0
    ...
networking:
  dnsDomain: cluster.local
  podSubnet: ""
  serviceSubnet: 10.96.0.0/12
nodeRegistration:
  criSocket: /var/run/dockershim.sock
  ...

YAML 文件是部署参数配置文件,你可以在 YAML 文件里填写各种自定义的部署参数。比如要指定 kube-apiserver 的参数,那么在文件里头加上如下内容即可。这样,kubeadm 就会用这些内容来替换 /etc/kubernetes /manifests/kube-apiserver.yaml 里的 command 字段里的参数了。

1
2
3
4
5
6
...
apiServerExtraArgs:
  advertise-address: 192.168.0.103
  anonymous-auth: false
  enable-admission-plugins: AlwaysPullImages,DefaultStorageClass
  audit-log-path: /home/johndoe/audit.log

除此之外,你还可以通过 YAML 文件

  • 修改 kubelet 和 kube-proxy 的配置,
  • 修改 kubernetes 使用的基础镜像的 URL(默认的 k8s.gcr.io/xxx 镜像在国内访问是有困难的)
  • 指定自己的证书文件
  • 指定特殊的容器运行时

3.4. 为什么 kubelet 不用容器方式部署

kubelet 是 Kubernetes 项目用来操作 Docker 等容器运行时的核心组件。可是除了跟容器运行时打交道外,kubelet 在配置网络、管理容器数据卷时,都需要操作宿主机。

而如果 kubelet 本身就运行在一个容器里,那么直接操作宿主机就会变得很麻烦。对于网络配置来说还好,kubelet 容器可以通过不开启 Network Namespace(即 Docker 的 host network 模式)的方式,直接共享宿主机的网络栈。可是,要让 kubelet 隔着容器的 Mount Namespace 和文件系统,操作宿主机的文件系统,就有点儿困难了。

比如,如果用户想要使用 NFS 做容器的持久化数据卷,那么 kubelet 就需要在容器进行绑定挂载前,在宿主机的指定目录上,先挂载 NFS 的远程目录。可是,这时候问题来了。由于现在 kubelet 是运行在容器里的,这就意味着它要做的这个“mount -F nfs”命令,被隔离在了一个单独的 Mount Namespace 中。即,kubelet 做的挂载操作,不能被“传播”到宿主机上。

对于这个问题,有人说可以使用 setns() 系统调用,在宿主机的 Mount Namespace 中执行这些挂载操作;也有人说,应该让 Docker 支持一个–mnt=host 的参数。但是,到目前为止,在容器里运行 kubelet,依然没有很好的解决办法,也不推荐你用容器去部署 Kubernetes 项目。

所以 kubeadm 采用的方式是将 kubelet 这个组件的二进制直接部署在宿主机上,而其他 kubernetes 组件则使用容器的方式部署起来。

3.5. 存在的问题

从上面我们可以看到,kubeadm 更多是将一个节点部署成 master 节点。所以说 kubeadm 欠缺的是一键部署一个高可用的 kubernetes 集群的能力,即 Etcd、Master 组件都应该是多节点集群,而不是单点的。

4. 巨人的肩膀

  1. https://kubernetes.io/zh/docs/reference/setup-tools/kubeadm/kubeadm-init/
  2. 极客时间,张磊,《深入剖析 kubernetes》

5. 实操-安装和使用

  • 首先是替换 apt-get 源,可以给 apt-get 添加一个源;

  • 下载 Docker 和 kubeadm(使用 apt-get 下载 kubeadm 的时候,kubeadm 往往已经包含了 kubelet 和 kubectl,假如没有这两个也需要下载);

  • 关闭 swap;

  • 获取镜像列表,预下载先,因为默认使用的镜像很难下载;

  • kubeadm init/kubeadm join;

  • 配置授权信息,以便 kubectl 可以访问 kube-apiserver;

  • 单节点,设置 master 节点也可以运行 Pod;

  • 假如提示没有网络,那么需要额外安装网络插件,比如 CNI 插件安装包

具体的安装步骤可以参考下面这几个网址:

  1. https://zhuanlan.zhihu.com/p/46341911

  2. https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/

  3. 极客时间,《Serverless入门课》

6. 附: kubeadm/kubectl 使用

6.1. kubeadm 命令

1
2
3
4
5
6
7
8
# 创建一个Master节点
$ kubeadm init
$ kubeadm init --config kubeadm.yaml

# 将一个Node节点加入到当前集群中
$ kubeadm join <Master节点的IP和端口>

kubeadm join 10.168.0.2:6443 --token 00bwbx.uvnaa2ewjflwu1ry --discovery-token-ca-cert-hash sha256:00eb62a2a6020f94132e3fe1ab721349bbcd3e9b94da9654cfe15f2985ebd711

6.2. kubectl 命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
kubectl config get-contexts		# 查看当前选中的集群
kubectl config use-context docker-desktop		# 切换集群为 docker-desktop

kubectl describe node master

kubectl describe pod nginx-deployment-67594d6bf6-9gdvr

kubectl get nodes

kubectl get pods
kubectl get pods -l app=nginx
kubectl get pods -n kube-system

kubectl get all	# 查看当前 k8s 环境的整体情况
kubectl get all -n kube-system	# 安装好的 k8s 系统运行状况

kubectl get secret regcred

kubectl create secret docker-registry regcred --docker-server=registry.cn-shanghai.aliyuncs.com --docker-username=你的容器镜像仓库用户名 --docker-password=你的容器镜像仓库密码

kubectl exec -it nginx-deployment-5c678cfb6d-lg9lw -- /bin/bash

kubectl create -f nginx-deployment.yaml
kubectl apply -f nginx-deployment.yaml
kubectl delete -f nginx-deployment.yaml
kubectl delete pod/你的 pod 名字


kubectl label nodes 1.1.1.1 role=nginx
kubectl label nodes 1.1.1.1 role-	# 删除一个Label,只需在命令行最后指定Label的key名并与一个减号相连即可

kubectl label nodes 1.1.1.1 role=apache --overwrite # 修改一个Label的值,需要加上 --overwrite 参数

6.3. 集群容器运行时清理

Containerd:

  1. /var/lib/containerd — 清空

  2. /var/run/containerd/containerd.sock — 清空

  3. Containerd 二进制文件(相关的二进制文件)— 清空/覆盖

  4. /etc/systemd/system/containerd.service — 清空/覆盖

  5. /etc/containerd/config.toml — 覆盖

Docker

  1. /var/lib/docker — 清空

  2. /var/run/docker.sock — 清空

  3. /var/run/dockershim.sock — 清空

  4. Docker 相关二进制(也会包含 containerd 相关) — 清空/覆盖

  5. /etc/systemd/system/docker.service — 清空/覆盖

巨人的肩膀

  1. 极客时间.《深入剖析Kubernetes》.张磊
  2. 《Kubernetes 权威指南》