本文作者:CODING - 王宽
背景
在 CODING 独立制品库产品的某个私有化项目交付过程中,因为底层 TCE 环境中的 TKE 服务无法使用,而项目交付时间又非常紧急的情况下,我们团队经过讨论决定采用多台 CVM 主机人工部署一套 Kubernetes 集群环境,一方面用于验证在特定环境中交付部署还存在哪些阻碍,另一方面也希望向客户传达我们正在努力解决问题的工作态度。
困难与挑战
客户的目标环境是一个 TCE 专有云平台,该云环境完全没有接入公网(出网/入网都没有),所以安装 Kubernetes 涉及到的工具、二进制可执行程序、依赖组件以及相关的镜像、服务都要做成能够离线部署的资源/物料,再通过现场同事带到机房环境中去拷贝。
目标环境的软件源服务无法使用
按正常情况 TCE 中包含了本地可用的软件源服务供 yum/apt 安装 Linux 组件、服务,但是实际情况是服务均无法使用,所以安装过程中涉及到的一些运行时、系统组件都只能预先在 rpm/deb 软件源仓库下载好,再打包到离线部署的物料中。
在我们的部署方案中,有状态的服务存储采用的是腾讯云 CBS、COS/CSP、TDSQL(MariaDB),即便是临时自建 Kubernetes 部署,还是希望能保持这样的永久存储架构。好处是当该环境中的 TKE 服务被修复后,我们只需要在 TKE 中重新部署一个实例,引用已经存在的 CBS、COS/CSP、TDSQL(MariaDB)实例就可以实现从自建 Kubernetes 的临时方案到基于 TKE 的生产方案间的无缝迁移。
基于 COS/CSP 的对象存储以及 TDSQL(MariaDB)都是在应用层通过网络访问的方式进行对接,不论是 TKE 还是自建 Kubernetes 互通协议都是一致的,唯独 CBS 需要通过 Kubernetes 中的 Persistent Volume 对象来挂载。在 TKE 中腾讯已经在底层做好了兼容和支持,而自建的 Kubernetes 默认并不兼容 CBS,需要对自建的 Kubernetes 进行一些改造才能兼容 CBS。
改造和兼容参考:https://github.com/TencentCloud/kubernetes-csi-tencentcloud/blob/master/docs/README_CBS_zhCN.md
好不容易找到上面的方案让自建的 Kubernetes 能够兼容 CBS 产品,在改造的过程中却发现 csi-controller.yaml 中的某个服务是通过引用腾讯云 SDK 去连接云平台(查询元数据/调用创建资源 API 等操作),而该 SDK 中对于云平台的接口终结点地址却是写死的腾讯公有云地址,如果要兼容私有云需要魔改镜像,蛋隐隐作疼。最后只能退而求其次使用 rancher 的 local-path-provisioner 来挂载存储。
规划资源
按照临时解决方案的情况,我规划了一个 VPC 网络(192.168.0.0/16),并需要在这个 VPC 网络中分别创建 3 台 CVM,其中 2 台用于部署 Kubernetes ,另外 1 台用做部署过程中需要使用的工作站,3 台 CVM 的配置如下:
类型 | CPU | 内存 | 硬盘 | 系统 | IP地址 | 备注 |
---|---|---|---|---|---|---|
CVM | 1C | 4GB | 50GB | Ubuntu 16 | 192.168.100.8 | 工作站 |
CVM | 4C | 16GB | 50GB | Ubuntu 16 | 192.168.100.10 | Kubernetes Master |
CVM | 8C | 32GB | 50GB | Ubuntu 16 | 192.168.100.11 | Kubernetes Worker Node |
准备 Docker 相关物料
在 https://packages.ubuntu.com/
中搜索相关的 deb 软件包,并下载到本地,我为我目标环境中的 CVM 准备了以下文件:
文件名 | 下载地址 |
---|---|
libseccomp2_2.4.3-1ubuntu3.16.04.3_amd64.deb | http://security.ubuntu.com/ubuntu/pool/main/libs/libseccomp/libseccomp2_2.4.3-1ubuntu3.16.04.3_amd64.deb |
docker-ce-cli_19.03.9_3-0_ubuntu-xenial_amd64.deb | https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/docker-ce_19.03.9~3-0~ubuntu-xenial_amd64.deb |
containerd.io_1.3.7-1_amd64.deb | https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/containerd.io_1.3.7-1_amd64.deb |
docker-ce_19.03.9_3-0_ubuntu-xenial_amd64.deb | https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/docker-ce_19.03.9~3-0~ubuntu-xenial_amd64.deb |
准备 kubeadmin 相关物料
参考了 https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl
文档,我们需要安装 kubelet
、kubeadm
、kubectl
,于是通过 https://apt.kubernetes.io
找到并下载相关的 debu 软件包。我为我目标环境中的 CVM 准备了以下文件:
文件名 | 下载地址 |
---|---|
kubernetes-cni_0.8.7-00_amd64_ca2303ea0eecadf379c65bad855f9ad7c95c16502c0e7b3d50edcb53403c500f.deb | https://packages.cloud.google.com/apt/pool/kubernetes-cni_0.8.7-00_amd64_ca2303ea0eecadf379c65bad855f9ad7c95c16502c0e7b3d50edcb53403c500f.deb |
socat_1.7.3.1-1_amd64.deb | http://kr.archive.ubuntu.com/ubuntu/pool/universe/s/socat/socat_1.7.3.1-1_amd64.deb |
ebtables_2.0.10.4-3.4ubuntu1_amd64.deb | http://kr.archive.ubuntu.com/ubuntu/pool/main/e/ebtables/ebtables_2.0.10.4-3.4ubuntu1_amd64.deb |
conntrack_1.4.3-3_amd64.deb | http://kr.archive.ubuntu.com/ubuntu/pool/main/c/conntrack-tools/conntrack_1.4.3-3_amd64.deb |
cri-tools_1.13.0-01_amd64_4ff4588f5589826775f4a3bebd95aec5b9fb591ba8fb89a62845ffa8efe8cf22.deb | https://packages.cloud.google.com/apt/pool/cri-tools_1.13.0-01_amd64_4ff4588f5589826775f4a3bebd95aec5b9fb591ba8fb89a62845ffa8efe8cf22.deb |
kubectl_1.19.3-00_amd64_8b135a2c8ab59784a12ae239cdfd80989d7d08d1da4449522c475cea1025eaa3.deb | https://packages.cloud.google.com/apt/pool/kubectl_1.19.3-00_amd64_8b135a2c8ab59784a12ae239cdfd80989d7d08d1da4449522c475cea1025eaa3.deb |
kubelet_1.19.3-00_amd64_03a16e592ababd5fbd11dc5c90503f166340d0f87efd69315892151df20f0a6a.deb | https://packages.cloud.google.com/apt/pool/kubelet_1.19.3-00_amd64_03a16e592ababd5fbd11dc5c90503f166340d0f87efd69315892151df20f0a6a.deb |
kubeadm_1.19.3-00_amd64_a46577b3dcf105747a609a72e4349d7efb51a059a005a1f77bea280b3d7b7724.deb | https://packages.cloud.google.com/apt/pool/kubeadm_1.19.3-00_amd64_a46577b3dcf105747a609a72e4349d7efb51a059a005a1f77bea280b3d7b7724.deb |
准备 Kubernetes 需要的服务镜像
我们知道 Kubernetes 集群除了 kubelet
是作为系统服务的形式运行,其他的 etcd
、kube-apiserver
、kube-proxy
等服务都是通过自身 Pod 去运行的,而 Pod 创建后需要从某个镜像仓库中拉取相应的镜像,此时因为没有访问公网的条件,我们就需要在目标环境的本地(工作站)运行一个准备好相应镜像资源的私有镜像仓库。
$ kubeadm config images list
k8s.gcr.io/kube-apiserver:v1.19.3
k8s.gcr.io/kube-controller-manager:v1.19.3
k8s.gcr.io/kube-scheduler:v1.19.3
k8s.gcr.io/kube-proxy:v1.19.3
k8s.gcr.io/pause:3.2
k8s.gcr.io/etcd:3.4.13-0
k8s.gcr.io/coredns:1.7.0
通过 kubeadm config images list
我们可以查看到执行初始化时,依赖以上的资源。
docker run -d -p 5000:5000 --name offline-image-provider registry:2
docker pull k8s.gcr.io/kube-apiserver:v1.19.3
docker tag k8s.gcr.io/kube-apiserver:v1.19.3 127.0.0.1:5000/kube-apiserver:v1.19.3
docker push 127.0.0.1:5000/kube-apiserver:v1.19.3
...
docker stop offline-image-provider
docker commit offline-image-provider offline-image-provider
docker rm offline-image-provider
docker save -o ~/offline-image-provider.tar offline-image-provider
通过以上脚本,我们可以在本地运行一个私有的镜像仓库,然后从 k8s.gcr.io
的仓库中拉取相关镜像,再推送到本地的私有仓库中,最后将这个私有仓库作为镜像保存成 tar 归档文件。但是因为需要进行多次 push 操作,而每一次对 offline-image-provider 的操作都会生成 docker layer,导致最后导出的 tar 归档文件过大,可以通过以下基于本地文件挂载、Docker Build 的方案来制作私有镜像仓库的离线物料:
FROM registry:2
COPY registry /var/lib/registry
docker run -d -p 5000:5000 -v ./registry:/var/lib/registry --name offline-image-provider offline-image-provider
docker pull k8s.gcr.io/kube-apiserver:v1.19.3
docker tag k8s.gcr.io/kube-apiserver:v1.19.3 127.0.0.1:5000/kube-apiserver:v1.19.3
docker push 127.0.0.1:5000/kube-apiserver:v1.19.3
...
docker stop offline-image-provider
docker rm offline-image-provider
docker build -t offline-image-provider .
docker save -o ~/offline-image-provider.tar offline-image-provider
在本节中我们需要准备以下物料:
文件名 | 备注 |
---|---|
offline-image-provider.tar | 私有镜像仓库镜像归档文件 |
准备 Pod network add-on 相关镜像
及部署清单文件
当我们通过 kubeadm
初始化一个集群后,我们还需要为 Kubernetes 集群准备一个网络插件用于实现 Container Network Interface(CNI),参考自:
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#pod-network
在资源规划中我们规划了这个集群只有一个工作节点,所以我们选择 Calico
的小于 50 个工作节点方案:
https://docs.projectcalico.org/getting-started/kubernetes/self-managed-onprem/onpremises#install-calico-with-kubernetes-api-datastore-50-nodes-or-less
首先需要下载 calico.yaml
文件,从这个文件中我们可以发现配置 calico 需要使用到一些镜像,这些镜像也要在办公网络拉取好相应的镜像并推送和打包到 offline-image-provider.tar 物料中(参考上一节),同时我们还需要编辑修改 calico.yaml
镜像仓库的地址,变更为即将在工作站中运行的私有镜像仓库地址,例如:
原镜像 | 变更为 |
---|---|
calico/cni:v3.16.3 | 192.168.100.8:5000/calico/cni:v3.16.3 |
calico/pod2daemon-flexvol:v3.16.3 | 192.168.100.8:5000/calico/pod2daemon-flexvol:v3.16.3 |
calico/node:v3.16.3 | 192.168.100.8:5000/calico/node:v3.16.3 |
calico/kube-controllers:v3.16.3 | 192.168.100.8:5000/calico/kube-controllers:v3.16.3 |
在本节中我们需要准备以下物料:
文件名 | 备注 |
---|---|
calico.yaml | Calico 网络插件部署清单文件 |
准备 Kubernetes csi tencentcloud 相关镜像
及部署清单文件
如 自建的 Kubernetes 与云平台不兼容 中提到的情况,我们需要借助于 kubernetes-csi-tencentcloud 来为自建的 Kubernetes 提供兼容 CBS 服务的能力。
参考自:
https://github.com/TencentCloud/kubernetes-csi-tencentcloud/blob/master/docs/README_CBS_zhCN.md
从文档中得知,我们需要准备以下部署清单文件:
文件名 |
---|
secret.yaml |
csi-controller-rbac.yaml |
csi-node-rbac.yaml |
csi-controller.yaml |
csi-node.yaml |
snapshot-crd.yaml |
并且从这些清单文件中得知,部署依赖以下镜像,和前面步骤一样,这些镜像也需要打包到 offline-image-provider.tar 物料中,同时需要修改部署清淡文件中对镜像的引用地址:
原镜像 | 变更为 |
---|---|
quay.io/k8scsi/csi-provisioner:v1.6.0 | 192.168.100.8:5000/k8scsi/csi-provisioner:v1.6.0 |
quay.io/k8scsi/csi-attacher:v2.2.0 | 192.168.100.8:5000/k8scsi/csi-attacher:v2.2.0 |
quay.io/k8scsi/csi-snapshotter:v2.1.0 | 192.168.100.8:5000/k8scsi/csi-snapshotter:v2.1.0 |
quay.io/k8scsi/snapshot-controller:v2.1.0 | 192.168.100.8:5000/k8scsi/snapshot-controller:v2.1.0 |
quay.io/k8scsi/csi-resizer:v0.5.0 | 192.168.100.8:5000/k8scsi/csi-resizer:v0.5.0 |
ccr.ccs.tencentyun.com/k8scsi/csi-tencentcloud-cbs:v1.2.0 | 192.168.100.8:5000/k8scsi/csi-tencentcloud-cbs:v1.2.0 |
quay.io/k8scsi/csi-node-driver-registrar:v1.2.0 | 192.168.100.8:5000/k8scsi/csi-node-driver-registrar:v1.2.0 |
ccr.ccs.tencentyun.com/k8scsi/csi-tencentcloud-cbs:v1.2.0 | 192.168.100.8:5000/k8scsi/csi-tencentcloud-cbs:v1.2.0 |
准备 Local path provisioner 相关镜像
及部署清单文件
如 自建的 Kubernetes 与云平台不兼容 中提到的,改造 Kubernetes 兼容 CBS 的过程中遇到了需要魔改镜像的问题,出于时间考虑,我们临时决定还是采用 Local path provisioner 方案。
参考自:
https://github.com/rancher/local-path-provisioner
从文档中得知,我们需要准备以下镜像,这些镜像需要打包到 offline-image-provider.tar 物料中。
原镜像 | 变更为 |
---|---|
rancher/local-path-provisioner:v0.0.18 | 192.168.100.8:5000/rancher/local-path-provisioner:v0.0.18 |
busybox | 192.168.100.8:5000/busybox |
在本节中我们需要准备以下物料:
文件名 | 备注 |
---|---|
local-path-storage.yaml | Local path provisioner 部署清单文件 |
部署
上传物料
将我们准备好的物料上传至 工作站 中,之后可以通过 scp
将相关物料文件从工作站传输到 Kubernetes Master、Kubernetes Worker Node 中。
安装 docker
通过以下脚本,分别在工作站、Kubernetes Master、Kubernetes Worker Node 中安装 docker 引擎:
sudo dpkg -i libseccomp2_2.4.3-1ubuntu3.16.04.3_amd64.deb
sudo dpkg -i docker-ce-cli_19.03.9_3-0_ubuntu-xenial_amd64.deb
sudo dpkg -i containerd.io_1.3.7-1_amd64.deb
sudo dpkg -i docker-ce_19.03.9_3-0_ubuntu-xenial_amd64.deb
安装 kubeadmin
通过以下脚本,分别在 Kubernetes Master、Kubernetes Worker Node 中安装 docker 引擎:
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system
sudo dpkg -i kubernetes-cni_0.8.7-00_amd64_ca2303ea0eecadf379c65bad855f9ad7c95c16502c0e7b3d50edcb53403c500f.deb
sudo dpkg -i socat_1.7.3.1-1_amd64.deb
sudo dpkg -i ebtables_2.0.10.4-3.4ubuntu1_amd64.deb
sudo dpkg -i conntrack_1.4.3-3_amd64.deb
sudo dpkg -i cri-tools_1.13.0-01_amd64_4ff4588f5589826775f4a3bebd95aec5b9fb591ba8fb89a62845ffa8efe8cf22.deb
sudo dpkg -i kubectl_1.19.3-00_amd64_8b135a2c8ab59784a12ae239cdfd80989d7d08d1da4449522c475cea1025eaa3.deb
sudo dpkg -i kubelet_1.19.3-00_amd64_03a16e592ababd5fbd11dc5c90503f166340d0f87efd69315892151df20f0a6a.deb
sudo dpkg -i kubeadm_1.19.3-00_amd64_a46577b3dcf105747a609a72e4349d7efb51a059a005a1f77bea280b3d7b7724.deb
sudo apt-mark hold kubelet kubeadm kubectl
在工作站中运行私有镜像仓库服务
在工作站中通过以下脚本启动我们准备好的镜像仓库服务,并监听 5000
端口:
docker load -i offline-image-provider.tar
docker run -d -p 5000:5000 --name offline-image-provider registry:2
初始化 kubernetes 集群
在 Kubernetes Master 中通过以下命令初始化一个 Kubernetes 集群实例:
kubeadm init --image-repository=192.168.100.8:5000
之后,你将看到类似以下输出结果:
[init] Using Kubernetes version: v1.19.3
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [kubeadm-cp localhost] and IPs [10.138.0.4 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [kubeadm-cp localhost] and IPs [10.138.0.4 127.0.0.1 ::1]
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubeadm-cp kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.138.0.4]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 31.501735 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-X.Y" in namespace kube-system with the configuration for the kubelets in the cluster
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "kubeadm-cp" as an annotation
[mark-control-plane] Marking the node kubeadm-cp as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node kubeadm-cp as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: <token>
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a Pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
/docs/concepts/cluster-administration/addons/
You can now join any number of machines by running the following on each node
as root:
kubeadm join <control-plane-host>:<control-plane-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>
根据提示运行以下脚本让普通用户使用 kubectl 管理集群:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
记录下提示中的 kubeadm join xxx
脚本,该脚本用于 Worker Node 加入集群时使用。
部署 Calico
在 Kubernetes Master 中通过以下脚本部署 Calico:
kubectl apply -f calico.yaml
等待数分钟后,你可以通过 kubectl get pod -n kube-system
查看 Kubernetes Pod 状态 。
将工作节点加入到集群中
在 Kubernetes Worker Node 中运行之前记录的 kubeadm join xxx
脚本,等待数分钟后,你可以在 Kubernetes Master 中通过 kubectl get node
查看到工作节点已经加入到了集群中。
部署 Local path provisioner
在 Kubernetes Master 中通过以下脚本部署 Local path provisioner:
kubectl apply -f local-path-storage.yaml
结语
通过以上步骤,我们已经成功在没有公网的环境基于自建虚拟机,自备离线物料的方式部署了一个标准版 Kubernetes 集群,并且通过指定 storageClassName
为 local-path
可以基于工作节点的本地存储创建 PersistentVolume。基于以上自备物料的方法,还可以将任意的服务部署到这个 Kubernetes 集群内。