完成一个 Kubernetes 集群的部署

  1. # 创建一个Master节点
  2. $ kubeadm init
  3. # 将一个Node节点加入到当前集群中
  4. $ kubeadm join

Kubernetes的每一个组件都是一个需要被执行的、单独的二进制文件。

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

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

在 Kubernetes 早期的部署脚本里,确实有一个脚本就是用 Docker 部署 Kubernetes 项目的

带来一个问题 如何容器化 kubelet。

kubelet 是 Kubernetes 项目用来操作 Docker 等容器运行时的核心组件。

Kubernetes 支持容器网络插件,使用的是一个名叫 CNI 的通用接口,它也是当前容器网络的事实标准,市面上的所有容器网络开源项目都可以通过 CNI 接入 Kubernetes

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

解决方法

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

使用 kubeadm 的第一步,是在机器上手动安装 kubeadm、kubelet 和 kubectl 这三个二进制文件。所以你只需要执行:

  1. apt-get install kubeadm

就可以使用“kubeadm init”部署 Master 节点了。

执行 kubeadm init 指令后,kubeadm 首先要做的,是一系列的检查工作,以确定这台机器可以用来部署 Kubernetes。

比如

  • Linux 内核的版本必须是否是 3.10 以上?
  • Linux Cgroups 模块是否可用?
  • 机器的 hostname 是否标准?
  • 在 Kubernetes 项目里,机器的名字以及一切存储在 Etcd 中的 API 对象,都必须使用标准的 DNS 命名(RFC 1123)。
  • 用户安装的 kubeadm 和 kubelet 的版本是否匹配?
  • 机器上是不是已经安装了 Kubernetes 的二进制文件?
  • Kubernetes 的工作端口 10250/10251/10252 端口是不是已经被占用?
  • ip、mount 等 Linux 指令是否存在?
  • Docker 是否已经安装?

kubeadm 要为你做的,是生成 Kubernetes 对外提供服务所需的各种证书和对应的目录。

Kubernetes 对外提供服务时,除非专门开启“不安全模式”,否则都要通过 HTTPS 才能访问 kube-apiserver。这就需要为 Kubernetes 集群配置好证书文件。

kubeadm 为 Kubernetes 项目生成的证书文件都放在 Master 节点的
/etc/kubernetes/pki 目录下。在这个目录下,最主要的证书文件是 ca.crt 和对应的私钥 ca.key。

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

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

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 组件,则更像是辅助性的系统容器。

在 kubeadm 中,Master 组件的 YAML 文件会被生成在 /etc/kubernetes/manifests 路径下。比如,kube-apiserver.yaml:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. annotations:
  5. kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.90.101.26:6443
  6. creationTimestamp: null
  7. labels:
  8. component: kube-apiserver
  9. tier: control-plane
  10. name: kube-apiserver
  11. namespace: kube-system
  12. spec:
  13. containers:
  14. - command:
  15. - kube-apiserver
  16. - --advertise-address=10.90.101.26
  17. - --allow-privileged=true
  18. - --authorization-mode=Node,RBAC
  19. - --client-ca-file=/etc/kubernetes/pki/ca.crt
  20. - --enable-admission-plugins=NodeRestriction
  21. - --enable-bootstrap-token-auth=true
  22. - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
  23. - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
  24. - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
  25. - --etcd-servers=https://127.0.0.1:2379
  26. - --insecure-port=0
  27. - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
  28. - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
  29. - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
  30. - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
  31. - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
  32. - --requestheader-allowed-names=front-proxy-client
  33. - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
  34. - --requestheader-extra-headers-prefix=X-Remote-Extra-
  35. - --requestheader-group-headers=X-Remote-Group
  36. - --requestheader-username-headers=X-Remote-User
  37. - --secure-port=6443
  38. - --service-account-issuer=https://kubernetes.default.svc.cluster.local
  39. - --service-account-key-file=/etc/kubernetes/pki/sa.pub
  40. - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
  41. - --service-cluster-ip-range=10.96.0.0/12
  42. - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
  43. - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
  44. image: k8s.gcr.io/kube-apiserver:v1.21.2
  45. imagePullPolicy: IfNotPresent
  46. livenessProbe:
  47. failureThreshold: 8
  48. httpGet:
  49. host: 10.90.101.26
  50. path: /livez
  51. port: 6443
  52. scheme: HTTPS
  53. initialDelaySeconds: 10
  54. periodSeconds: 10
  55. timeoutSeconds: 15
  56. name: kube-apiserver
  57. readinessProbe:
  58. failureThreshold: 3
  59. httpGet:
  60. host: 10.90.101.26
  61. path: /readyz
  62. port: 6443
  63. scheme: HTTPS
  64. periodSeconds: 1
  65. timeoutSeconds: 15
  66. resources:
  67. requests:
  68. cpu: 250m
  69. startupProbe:
  70. failureThreshold: 24
  71. httpGet:
  72. host: 10.90.101.26
  73. path: /livez
  74. port: 6443
  75. scheme: HTTPS
  76. initialDelaySeconds: 10
  77. periodSeconds: 10
  78. timeoutSeconds: 15
  79. volumeMounts:
  80. - mountPath: /etc/ssl/certs
  81. name: ca-certs
  82. readOnly: true
  83. - mountPath: /etc/pki
  84. name: etc-pki
  85. readOnly: true
  86. - mountPath: /etc/kubernetes/pki
  87. name: k8s-certs
  88. readOnly: true
  89. hostNetwork: true
  90. priorityClassName: system-node-critical
  91. volumes:
  92. - hostPath:
  93. path: /etc/ssl/certs
  94. type: DirectoryOrCreate
  95. name: ca-certs
  96. - hostPath:
  97. path: /etc/pki
  98. type: DirectoryOrCreate
  99. name: etc-pki
  100. - hostPath:
  101. path: /etc/kubernetes/pki
  102. type: DirectoryOrCreate
  103. name: k8s-certs
  104. status: {}
  1. 这个 Pod 里只定义了一个容器,它使用的镜像是:k8s.gcr.io/kube-apiserver-amd64:v1.11.1 。这个镜像是 Kubernetes 官方维护的一个组件镜像。
  2. 这个容器的启动命令(commands)是 kube-apiserver —authorization-mode=Node,RBAC …,这样一句非常长的命令。其实,它就是容器里 kube-apiserver 这个二进制文件再加上指定的配置参数而已。

在这一步完成后,kubeadm 还会再生成一个 Etcd 的 Pod YAML 文件,用来通过同样的 Static Pod 的方式启动 Etcd。所以,最后 Master 组件的 Pod YAML 文件如下所示:

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

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

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

然后,kubeadm 就会为集群生成一个 bootstrap token。在后面,只要持有这个 token,任何一个安装了 kubelet 和 kubadm 的节点,都可以通过 kubeadm join 加入到这个集群当中。

在 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 了。

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

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

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

配置 kubeadm 的部署参数

要指定 kube-apiserver 的启动参数

推荐使用 kubeadm init 部署 Master 节点时,使用下面这条指令:

  1. $ kubeadm init --config kubeadm.yaml

可以给 kubeadm 提供一个 YAML 文件(比如,kubeadm.yaml),它的内容如下所示(部分)

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

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

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

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

而 kubeadm 的源代码,直接就在 kubernetes/cmd/kubeadm 目录下

如果有部署规模化生产环境的需求,推荐使用kops或者 SaltStack 这样更复杂的部署工具。

遇到的坑

建议先手动把镜像pull 下来,从阿里的镜像源上,然后tag成安装所需的镜像名称,这样你发现安装过程会异常顺利

解决拉取google镜像问题,有两种方式推荐
1. 拉取hub.docker上gcrxio同步的k8s镜像到本地修改repository为k8s.gcr.io后,使用kubeadmin顺利安装
2. 使用阿里的容器镜像服务,拉取hub.docker上gcrxio同步的k8s镜像推送到你的镜像库中。安装时,kubeadm init with a configuration file,在configuration file中修改相关的镜像地址为阿里容器服务的镜像地址

只是kubeadm搭建高可用集群如果完全按照官方文档来,它生成的的证书只有一年期限。所以需要自己提前做好证书。