谈一手:用 kubeadm 搭建 Kubernetes

1,822 阅读4分钟

Kubernetes的搭建方式有很多种,总结一下网上常见的部署方式:

  • 二进制包部署方式:难度较大,除了将 Kubernetes 的各个组件编译成二进制文件外,还要负责为这些二进制文件编写对应的配置文件、配置自启动脚本,以及为 kube-apiserver 配置授权文件等等诸多运维工作。常用的诸如SaltStack、Ansible 等运维工具本身的学习成本,就可能比 Kubernetes 项目还要高。

  • minnikube或kubeadm部署方式:官方推荐的部署方式,运维、部署难度大大降低。把 kubelet 直接运行在宿主机上,然后使用容器部署其他的 Kubernetes 组件。

  • 其它解决方案:比如 Rancher,Kubesphere,KubeOperator 这些方式大大降低了部署、运维、使用的难度,且有详细的官方文档。

笔者推荐先试试Kubeadm,然后再试试其它工具,Kubeadm的部署方式是把 kubelet 直接运行在宿主机上,然后使用容器部署其他的 Kubernetes 组件。所以装完之后,各个组件也就大概熟悉了。

先来说说 kubeadm 的工作原理

Kubernetes 在部署时,它的每一个组件都是一个需要被执行的、单独的二进制文件,所以不难想象可以把这些二进制文件放入到指定的机器中然后编写控制脚本来启停这些组件,但是在了解了容器技术后我们能不能用容器部署 Kubernetes ?

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

理想是美好,但现实是残酷的,这其中有一个非常麻烦的问题,即:如何容器化 kubelet

kubelet 是 Kubernetes 项目用来操作 Docker 等容器运行时的核心组件。可是,除了跟容器运行时打交道外,kubelet 在配置容器网络、管理容器数据卷时,都需要直接操作宿主机。 如果让 kubelet 本身都运行在容器里,隔着容器的 Mount Namespace 和文件系统,操作宿主机的文件系统,就有点儿困难。

因此,kubeadm 用了一个折中的办法

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

现在我们来试着搭建一下,注意:使用 kubeadm 部署 Kubernetes 会有翻墙的问题

先安装docker,选择指定版本

apt-cache madison docker-ce
sudo apt-get install docker-ce=17.03.0-ce

再安装 kubelet,kubelet,kubelet(请安装对应版本,否则之后的操作可能会版本不同有冲突)

apt install kubelet=1.11.3-00
apt install kubectl=1.11.3-00
apt install kubeadm=1.11.3-00

部署 Kubernetes 的 Master 节点

编写一个给 kubeadm 用的 YAML 文件(名叫:kubeadm.yaml)

apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
controllerManagerExtraArgs:
  horizontal-pod-autoscaler-use-rest-clients: "true"
  horizontal-pod-autoscaler-sync-period: "10s"
  node-monitor-grace-period: "10s"
apiServerExtraArgs:
  runtime-config: "api/all=true"
kubernetesVersion: "stable-1.11"

这个配置中,我给 kube-controller-manager 设置了:

horizontal-pod-autoscaler-use-rest-clients: "true"

意味着,将来部署的 kube-controller-manager 能够使用自定义资源(Custom Metrics)进行自动水平扩展。

之后我们只需要执行一条指令

$ kubeadm init --config kubeadm.yaml

就可以完成 Kubernetes Master 的部署了,这个过程中 kubeadm 会做一系列的检查工作,以确定这台机器可以用来部署 Kubernetes。这一步检查,我们称为“Preflight Checks”。

在通过了 Preflight Checks 之后,kubeadm 要为你做的,是生成 Kubernetes 对外提供服务所需的各种证书和对应的目录,证书文件都放在 Master 节点的 /etc/kubernetes/pki 目录下。在这个目录下,最主要的证书文件是 ca.crt 和对应的私钥 ca.key。

此外,用户使用 kubectl 获取容器日志等 streaming 操作时,需要通过 kube-apiserver 向 kubelet 发起请求,这个连接也必须是安全的。kubeadm 为这一步生成的是 apiserver-kubelet-client.crt 文件,对应的私钥是 apiserver-kubelet-client.key。

接下来,kubeadm 会为 Master 组件生成 Pod 配置文件,Kubernetes 的三个 Master 组件 kube-apiserver、kube-controller-manager、kube-scheduler,都会被使用 Pod 的方式部署起来

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

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

部署完成后,kubeadm 会生成一行指令:

kubeadm join 10.211.55.4:6443 --token bow08c.5z981pawlm0rdwof --discovery-token-ca-cert-hash sha256:a613e9d0639b2c9e9fe6e7b03fe5e01aaa80d9e4c4a2888b680431990e83a7b5

这个 kubeadm join 命令,就是用来给这个 Master 节点添加更多工作节点(Worker)的命令。我们在后面部署 Worker 节点的时候马上会用到它,所以找一个地方把这条命令记录下来。

此外,kubeadm 还会提示我们第一次使用 Kubernetes 集群所需要的配置命令:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Kubernetes 集群默认需要加密方式访问。所以,这几条命令,就是将刚刚部署生成的 Kubernetes 集群的安全配置文件,保存到当前用户的.kube 目录下,kubectl 默认会使用这个目录下的授权信息访问 Kubernetes 集群。

可以用 kubectl get 来查看当前唯一结点的状态

kubectl get nodes
NAME      STATUS    ROLES     AGE       VERSION
ubuntu    NotReady  master    8d        v1.11.3

这时候 get 指令输出的结果里,Master 节点的状态是 NotReady,此时可以用 kubectl describe 来查看这个节点(Node)对象的详细信息、状态和事件(Event)

kubectl describe node ubuntu

NotReady 的原因是我们尚未部署任何网络插件。 在 Kubernetes 项目“一切皆容器”的设计理念指导下,部署网络插件非常简单,只需要执行一句 kubectl apply 指令,以 Weave 为例:

$ kubectl apply -f https://git.io/weave-kube-1.6

部署完成后,我们可以通过 kubectl get 检查 Pod 的状态

kubectl get pods -n kube-system
NAME                             READY     STATUS    RESTARTS   AGE
coredns-78fcdf6894-kr6hk         1/1       Running   1          8d
coredns-78fcdf6894-wm77c         1/1       Running   1          8d
etcd-ubuntu                      1/1       Running   1          8d
kube-apiserver-ubuntu            1/1       Running   3          8d
kube-controller-manager-ubuntu   1/1       Running   2          8d
kube-proxy-sjbjq                 1/1       Running   1          8d
kube-scheduler-ubuntu            1/1       Running   3          8d
weave-net-nbpd8                  2/2       Running   3          8d

可以看到,所有的系统 Pod 都成功启动了!

至此,Kubernetes 的 Master 节点就部署完成了。如果你只需要一个单节点的 Kubernetes,现在你就可以使用了。

Kubernetes 的 Worker 节点跟 Master 节点几乎是相同的,它们运行着的都是一个 kubelet 组件。唯一的区别在于,在 kubeadm init 的过程中,kubelet 启动后,Master 节点上还会自动运行 kube-apiserver、kube-scheduler、kube-controller-manger 这三个系统 Pod。

所以,相比之下,部署 Worker 节点反而是最简单的,只需要两步即可完成。

第一步,在所有 Worker 节点上执行“安装 kubeadm 和 Docker”一节的所有步骤。

第二步,执行部署 Master 节点时生成的 kubeadm join 指令:

kubeadm join 10.211.55.4:6443 --token bow08c.5z981pawlm0rdwof --discovery-token-ca-cert-hash sha256:a613e9d0639b2c9e9fe6e7b03fe5e01aaa80d9e4c4a2888b680431990e83a7b5

默认情况下 Master 节点是不允许运行用户 Pod 的。而 Kubernetes 做到这一点,依靠的是 Kubernetes 的 Taint/Toleration 机制。

它的原理非常简单:一旦某个节点被加上了一个 Taint,即被“打上了污点”,那么所有 Pod 就都不能在这个节点上运行,因为 Kubernetes 的 Pod 都有“洁癖”。

除非,有个别的 Pod 声明自己能“容忍”这个“污点”,即声明了 Toleration,它才可以在这个节点上运行。

为节点打上“污点”(Taint)的命令是:

kubectl taint nodes node1 foo=bar:NoSchedule

这时,该节点上就会增加一个键值对格式的 Taint,即:foo=bar:NoSchedule。其中值里面的 NoSchedule,意味着这个 Taint 只会在调度新 Pod 时产生作用,而不会影响已经在 node1 上运行的 Pod,哪怕它们没有 Toleration。

Pod 声明 Toleration 的方式,在 Pod 的.yaml 文件中的 spec 部分,加入 tolerations 字段:

apiVersion: v1
kind: Pod
...
spec:
  tolerations:
  - key: "foo"
    operator: "Equal"
    value: "bar"
    effect: "NoSchedule"

这个 Toleration 的含义是,这个 Pod 能“容忍”所有键值对为 foo=bar 的 Taint( operator: “Equal”,“等于”操作)。

到了这一步,一个基本完整的 Kubernetes 集群就部署完毕了。

部署 Dashboard 可视化插件

在 Kubernetes 社区中,有一个很受欢迎的 Dashboard 项目,它可以给用户提供一个可视化的 Web 界面来查看当前集群的各种信息。

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc6/aio/deploy/recommended.yaml

部署完成之后,我们就可以查看 Dashboard 对应的 Pod 的状态了:

kubectl get pods -o wide --all-namespaces

kubernetes-dashboard   dashboard-metrics-scraper-878cb9dc4-cdmnb    1/1       Running   1          1h       

需要注意的是,由于 Dashboard 是一个 Web Server,很多人经常会在自己的公有云上无意地暴露 Dashboard 的端口,从而造成安全隐患,默认只能通过 Proxy 的方式在本地访问,你可以查看 Dashboard 项目的官方文档

 kubectl proxy

此时可以访问地址:

http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

部署容器存储插件 在前面介绍容器原理时已经提到过,很多时候我们需要用数据卷(Volume)把外面宿主机上的目录或者文件挂载进容器的 Mount Namespace 中,从而达到容器和宿主机共享这些目录或者文件的目的。容器里的应用,也就可以在这些数据卷中新建和写入文件。

可是,如果你在某一台机器上启动的一个容器,显然无法看到其他机器上的容器在它们的数据卷里写入的文件。这是容器最典型的特征之一:无状态。

无状态的意思是:无法看到容器内部写入的东西,在其他容器或者宿主机看来,这个容器是没有任何变化的,所以无论变化不变化都没有区别,所以叫无状态。

而容器的持久化存储,就是用来保存容器存储状态的重要手段:存储插件会在容器里挂载一个基于网络或者其他机制的远程数据卷,使得在容器里创建的文件,实际上是保存在远程存储服务器上,或者以分布式的方式保存在多个节点上,而与当前宿主机没有任何绑定关系。这样,无论你在其他哪个宿主机上启动新的容器,都可以请求挂载指定的持久化存储卷,从而访问到数据卷里保存的内容。这就是“持久化”的含义。

Rook 项目是一个基于 Ceph 的 Kubernetes 存储插件(它后期也在加入对更多存储实现的支持)。不过,不同于对 Ceph 的简单封装,Rook 在自己的实现中加入了水平扩展、迁移、灾难备份、监控等大量的企业级功能,使得这个项目变成了一个完整的、生产级别可用的容器存储插件。

得益于容器化技术,用几条指令,Rook 就可以把复杂的 Ceph 存储后端部署起来:

$ kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/cluster/examples/kubernetes/ceph/common.yaml

$ kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/cluster/examples/kubernetes/ceph/operator.yaml

$ kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/cluster/examples/kubernetes/ceph/cluster.yaml

其实,在很多时候,大家说的所谓“云原生”,就是“Kubernetes 原生”的意思。而像 Rook、Istio 这样的项目,正是贯彻这个思路的典范。在我们后面讲解了声明式 API 之后,相信你对这些项目的设计思想会有更深刻的体会。