深入剖析Kubernetes学习笔记:Kubernetes集群搭建与实践(10)

一、难道 Kubernetes 项目就没有简单的部署方法了吗?

这个问题,在 Kubernetes 社区里一直没有得到足够重视。直到 2017 年,在志愿者的推动下,社区才终于发起了一个独立的部署工具,名叫:kubeadm

这个项目的目的,就是要让用户能够通过这样两条指令完成一个 Kubernetes 集群的部署:

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

是不是非常方便呢?

不过,你可能也会有所顾虑:

Kubernetes 的功能那么多,这样一键部署出来的集群,能用于生产环境吗?

到目前为止(2018 年 9 月),这个问题的答案是:不能。

因为 kubeadm 目前最欠缺的是,一键部署一个高可用的 Kubernetes 集群,即:Etcd、Master 组件都应该是多节点集群,而不是现在这样的单点。这,当然也正是 kubeadm 接下来发展的主要方向。

另一方面,Lucas 也正在积极地把 kubeadm phases 开放给用户,即:用户可以更加自由地定制 kubeadm 的每一个部署步骤。这些举措,都可以让这个项目更加完善,我对它的发展走向也充满了信心。

当然,如果你有部署规模化生产环境的需求,我推荐使用kops或者 SaltStack 这样更复杂的部署工具。但,在本专栏接下来的讲解中,我都会以 kubeadm 为依据进行讲述。

  • 一方面,作为 Kubernetes 项目的原生部署工具,kubeadm 对 Kubernetes 项目特性的使用和集成,确实要比其他项目“技高一筹”,非常值得我们学习和借鉴;
  • 另一方面,kubeadm 的部署方法,不会涉及到太多的运维工作,也不需要我们额外学习复杂的部署工具。而它部署的 Kubernetes 集群,跟一个完全使用二进制文件搭建起来的集群几乎没有任何区别。

因此,使用 kubeadm 去部署一个 Kubernetes 集群,对于你理解 Kubernetes 组件的工作方式和架构,最好不过了。

为了回答这个问题,在今天这篇文章,我就先和你介绍一下 kubeadm 的工作原理吧

二、kubeadm 的工作原理

1、为什么不用容器部署 Kubernetes 呢?

这样,我只要给每个 Kubernetes 组件做一个容器镜像,然后在每台宿主机上用 docker run 指令启动这些组件容器,部署不就完成了吗?

事实上,在 Kubernetes 早期的部署脚本里,确实有一个脚本就是用 Docker 部署 Kubernetes 项目的,这个脚本相比于 SaltStack 等的部署方式,也的确简单了不少。

2、但是,这样做会带来一个很麻烦的问题,即:如何容器化 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 项目

3、正因为如此,kubeadm 选择了一种妥协方案:

把 kubelet 直接运行在宿主机上,然后使用容器部署其他的 Kubernetes 组件。

所以,你使用 kubeadm 的第一步,是在机器上手动安装 kubeadm、kubelet 和 kubectl 这三个二进制文件。当然,kubeadm 的作者已经为各个发行版的 Linux 准备好了安装包,所以你只需要执行:

$ apt-get install kubeadm

就可以了。

接下来,你就可以使用“kubeadm init”部署 Master 节点了。

三、kubeadm init 的工作流程

当你执行 kubeadm init 指令后,kubeadm 首先要做的,是一系列的检查工作,以确定这台机器可以用来部署 Kubernetes。这一步检查,我们称为“Preflight Checks”,它可以为你省掉很多后续的麻烦。

1、Preflight Checks

其实,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 是否已经安装?

2、生成 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 为你生成这些证书,而是拷贝现有的证书到如下证书的目录里:

[root@master1 kubernetes]# pwd
/etc/kubernetes
[root@master1 kubernetes]# tree
.
├── kubelet.kubeconfig
├── kube-proxy.kubeconfig
└── ssl
    ├── admin.csr
    ├── admin-csr.json
    ├── admin-key.pem
    ├── admin.pem
    ├── aggregator-proxy.csr
    ├── aggregator-proxy-csr.json
    ├── aggregator-proxy-key.pem
    ├── aggregator-proxy.pem
    ├── ca-config.json
    ├── ca.csr
    ├── ca-csr.json
    ├── ca-key.pem
    ├── ca.pem
    ├── kubelet.csr
    ├── kubelet-csr.json
    ├── kubelet-key.pem
    ├── kubelet.pem
    ├── kube-proxy.csr
    ├── kube-proxy-csr.json
    ├── kube-proxy-key.pem
    ├── kube-proxy.pem
    ├── kubernetes.csr
    ├── kubernetes-csr.json
    ├── kubernetes-key.pem
    └── kubernetes.pem

1 directory, 27 files

这时,kubeadm 就会跳过证书生成的步骤,把它完全交给用户处理。

3、kubeadm 接下来会为其他组件生成访问 kube-apiserver 所需的配置文件

这些文件的路径是:/etc/kubernetes/xxx.conf:

[root@master1 kubernetes]# ls
kubelet.kubeconfig  kube-proxy.kubeconfig  ssl

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

4、接下来,kubeadm 会为 Master 组件生成 Pod 配置文件

接下来,kubeadm 会为 Master 组件生成 Pod 配置文件。我已经在上一篇文章中和你介绍过 Kubernetes 有三个 Master 组件 kube-apiserver、kube-controller-manager、kube-scheduler,而它们都会被使用 Pod 的方式部署起来。

你可能会有些疑问:这时,Kubernetes 集群尚不存在,难道 kubeadm 会直接执行 docker run 来启动这些容器吗?

当然不是。

在 Kubernetes 中,有一种特殊的容器启动方法叫做“Static Pod”。它允许你把要部署的 Pod 的 YAML 文件放在一个指定的目录里。这样,当这台机器上的 kubelet 启动时,它会自动检查这个目录,加载所有的 Pod YAML 文件,然后在这台机器上启动它们。

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

[root@master1 manifests]# pwd
/opt/kubeOperator-kube-bin/1.16.4/manifests
[root@master1 manifests]# ls
dashboard  ingress  kubeapps-plus  prometheus  storage  weave-scope
[root@master1 manifests]# cd dashboard/
[root@master1 dashboard]# ll
total 12
-rw-r--r--. 1 root root 5810 Feb 18 09:07 kubernetes-dashboard.yaml
-rw-r--r--. 1 root root 3383 Feb 18 09:07 metrics-server.yaml

而一旦这些 YAML 文件出现在被 kubelet 监视的 /etc/kubernetes/manifests 目录下,kubelet 就会自动创建这些 YAML 文件中定义的 Pod,即 Master 组件的容器。

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

5、然后,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 就可以了。

四、kubeadm join 的工作流程

这个流程其实非常简单,kubeadm init 生成 bootstrap token 之后,你就可以在任意一台安装了 kubelet 和 kubeadm 的机器上执行 kubeadm join 了。

1、可是,为什么执行 kubeadm join 需要这样一个 token 呢?

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

所以,kubeadm 至少需要发起一次“不安全模式”的访问到 kube-apiserver,从而拿到保存在 ConfigMap 中的 cluster-info(它保存了 APIServer 的授权信息)。而 bootstrap token,扮演的就是这个过程中的安全验证的角色。

只要有了 cluster-info 里的 kube-apiserver 的地址、端口、证书,kubelet 就可以以“安全模式”连接到 apiserver 上,这样一个新的节点就部署完成了。

接下来,你只要在其他节点上重复这个指令就可以了。

五、配置 kubeadm 的部署参数

我在前面讲解了 kubeadm 部署 Kubernetes 集群最关键的两个步骤,kubeadm init 和 kubeadm join。相信你一定会有这样的疑问:kubeadm 确实简单易用,可是我又该如何定制我的集群组件参数呢?

比如,我要指定 kube-apiserver 的启动参数,该怎么办?

在这里,我强烈推荐你在使用 kubeadm init 部署 Master 节点时,使用下面这条指令:

$ kubeadm init --config kubeadm.yaml

这时,你就可以给 kubeadm 提供一个 YAML 文件(比如,kubeadm.yaml),它的内容如下所示(我仅列举了主要部分):

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
  ...

通过制定这样一个部署参数配置文件,你就可以很方便地在这个文件里填写各种自定义的部署参数了。比如,我现在要指定 kube-apiserver 的参数,那么我只要在这个文件里加上这样一段信息:

...
apiServerExtraArgs:
  advertise-address: 192.168.0.103
  anonymous-auth: false
  enable-admission-plugins: AlwaysPullImages,DefaultStorageClass
  audit-log-path: /home/johndoe/audit.log

然后,kubeadm 就会使用上面这些信息替换 /etc/kubernetes/manifests/kube-apiserver.yaml 里的 command 字段里的参数了。

而这个 YAML 文件提供的可配置项远不止这些。比如,你还可以修改 kubelet 和 kube-proxy 的配置,修改 Kubernetes 使用的基础镜像的 URL(默认的k8s.gcr.io/xxx镜像 URL 在国内访问是有困难的),指定自己的证书文件,指定特殊的容器运行时等等。这些配置项,就留给你在后续实践中探索了。

六、经典留言

1、Antergone

有一个ansible playbook可以推荐给大家。
https://github.com/gjmzj/kubeasz
初学者可以跟着一步步看原理,后期还可以自己定制化。主要是容易产生兴趣。

2、MiracleWong

我也补充一个可用于部署生产级别的Kubernetes的开源项目:
https://github.com/kubernetes-incubator/kubespray 
我们公司正在使用。

3、yandd

推荐个k8s实验平台
https://console.magicsandbox.com
可能需要fan qiang才能访问

4、blackpiglet

1. Linux 下生成证书,主流的选择应该是 OpenSSL,还可以使用 GnuGPG,或者 keybase。
2. Kubernetes 组件之间的交互方式:HTTP/HTTPS、gRPC、DNS、系统调用等。

5、alex

对墙经验丰富的人来了,可以用下面这个镜像

https://github.com/anjia0532/gcr.io_mirror
posted @ 2020-02-19 21:44  活的潇洒80  阅读(896)  评论(0编辑  收藏  举报