Kubernetes(k8s)是一个容器编排系统,简单而言用来管一堆容器。k8s 的有效部署称为 k8s 集群。网上都用的 CentOS,考虑到它被弃坑,我用 Ubuntu 20 演示。

环境

  • 四个 2C2G 阿里云 ECS,放在一个私网。
  • 都是 Ubuntu 系统。

基础概念

K8S 集群的工作方式:X Master Node + Y Worker Node. 如果 Master 节点 down 了,可以通过选举或者其他策略选出新的 master.

image-20220109093059935

集群可以分为两部分:Control Plane 和 Nodes. 二者之间通过 API server 交互。直接发生交互的是 api 和 kubelet(相当于 Node 内的组长)。kube-proxy 负责应用间所有的访问,类似计网中的路由器。不同 node 的 kube-proxy 之间会进行通信,串通消息。kube-controller-manager 是一个守护进程,用于调节系统状态。

Node 内部可以运行多个应用,调度信息通过 kubelet 与 master 交换。

而无论是 Master Node 还是 Worker Node,都是在容器中运行。

凌驾于整个集群之上的,则是 kubectl,用于启动整个集群。

部署和安装

题外话

实际工程中很可能不需要我们如此细致地部署,并且公司会提供镜像仓库乃至专线,不容易被 GFW 搞坏心态。另外如果推荐阅读阿里云的教程:自建 K8S 集群迁移 ACK 弹性裸金属集群。

总览

使用部署工具安装 Kubernetes | Kubernetes

  • 我们需要在所有机器(Node)上安装 kubelet。至少一个节点安装 kubectl。可以用 kubeadm 简化部署.
  • 我们挑选一个 node 作为 master,通过 kubeadm init 初始化主节点。之后 kubelet 会把 scheduler, kube-proxy, api-server, etcd, controller-manager 等应用装起来。
  • 其它节点通过 kubeadm join 加入集群。kube-proxy 会被 kubelet 安装到从节点。

image-20220109101617738

(其实无所谓打码了,今晚这几个实例就要被销毁。)可以利用 electerm 的批量执行看看能不能 ping 通。

我的四个节点:

Host "k8s_master"
    HostName 39.108.232.92
    # 10.0.0.100
    
Host "k8s_node2"
    HostName 120.24.194.30
    # 10.0.0.98
    
Host "k8s_node3"
    HostName 120.24.94.76
    # 10.0.0.99
    
Host "k8s_node4"
    HostName 120.24.173.212
    # 10.0.0.97

安装 docker

并非必须 docker,详见 容器运行时 | Kubernetes

Install Docker Engine on Ubuntu | Docker Documentation

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo   "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install docker-ce docker-ce-cli containerd.io -y

国内备选方案

安装所需的软件

apt-get update
apt-get install -y apt-transport-https gnupg-agent software-properties-common

添加阿里云安装源的密钥

curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -

添加阿里云安装源

add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"

安装最新版 docker engine

apt update
apt install docker-ce

预备安装 k8s

注意:

  • 每台机器至少 2C2G
  • hostname, mac, product_uuid 都要唯一。
    • 主机名可以通过 hostnamectl set-hostname NAME 设置
  • 关掉 selinux 大坑。
  • 生产环境禁用交换分区。
    • 可以通过 free -m 查看。
    • 可以通过 swapoff -a 关闭
  • 一般内网机器直接关闭软件防火墙,但是用 k8s 一般不能关。

lsmod | grep br_netfilter 确保 br_netfilter 启用。手动启动:sudo modprobe br_netfilter

确保 iptables 可以检查桥接流量。

安装 kubeadm、kubelet、kubectl

下面照抄。以 [安装 kubeadm | Kubernetes](https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/install-kubeadm/# 安装 - kubeadm-kubelet - 和 - kubectl) 为准。

  1. 更新 apt 包索引并安装使用 Kubernetes apt 仓库所需要的包:

    apt-get update
    apt-get install -y apt-transport-https ca-certificates curl
    
  2. 下载 Google Cloud 公开签名秘钥:

    curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
    
  3. 添加 Kubernetes apt 仓库:

    "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | tee /etc/apt/sources.list.d/kubernetes.list
    
  4. 更新 apt 包索引,安装 kubelet、kubeadm 和 kubectl,并锁定其版本:

    sudo apt-get update
    sudo apt-get install -y kubelet kubeadm kubectl
    sudo apt-mark hold kubelet kubeadm kubectl
    

国内备选方案

Debian 系

添加阿里云安装源

curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF > /etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

添加安装源密钥

gpg --keyserver keyserver.ubuntu.com --recv-keys BA07F4FB
gpg --export --armor BA07F4FB | sudo apt-key add -

安装三驾马车

apt update && \
apt install -y kubelet kubeadm kubectl && \
apt-mark hold kubelet kubeadm kubectl

Redhat 系

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
   http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF


sudo yum install -y kubelet-1.20.9 kubeadm-1.20.9 kubectl-1.20.9 --disableexcludes=kubernetes

sudo systemctl enable --now kubelet

对所有节点,切换到 systemd 控制

cat > /etc/docker/daemon.json <<EOF
{
    "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
    "log-opts": {
    "max-size": "100m"
    },
    "storage-driver": "overlay2",
    "registry-mirrors":[
        "https://kfwkfulq.mirror.aliyuncs.com",
        "https://2lqq34jg.mirror.aliyuncs.com",
        "https://pee6w651.mirror.aliyuncs.com",
        "http://hub-mirror.c.163.com",
        "https://docker.mirrors.ustc.edu.cn",
        "https://registry.docker-cn.com"
    ]
}
EOF

修改 kubelet:

mkdir /var/lib/kubelet
cat > /var/lib/kubelet/config.yaml <<EOF
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF

重启 docker 与 kubelet:

systemctl daemon-reload
systemctl restart docker
systemctl restart kubelet

初始化 Master

kubeadm config print init-defaults > kubeadm-init.yaml

这里面的数据根据情况配置

注意:实际上我用了配置文件反而怎么都不成功。可以试试直接 kubeadm init

失败重置用 kubeadm reset

[reset] FYI: You can look at this config file with ‘kubectl -n kube-system get cm kubeadm-config -o yaml’

apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
  - system:bootstrappers:kubeadm:default-node-token
  token: abcdef.0123456789abcdef
  ttl: 24h0m0s
  usages:
  - signing
  - authentication
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 39.108.232.92
  bindPort: 6443
nodeRegistration:
  criSocket: /var/run/dockershim.sock
  imagePullPolicy: IfNotPresent
  name: node # HERE!
  taints: null
---
apiServer:
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: 1.23.0
networking:
  dnsDomain: cluster.local
  serviceSubnet: 10.0.0.0/12 # HERE!
scheduler: {}

GFW:

imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers

完事儿拉取镜像:

kubeadm config images pull --config kubeadm-init.yaml

初始化:

kubeadm init --config kubeadm-init.yaml

重申,实际上我用了配置文件反而怎么都不成功。可以试试直接 kubeadm init

另外,如果总是超时,有可能是 GFW 在作怪。参阅 云服务器如何突破 GFW?SSH 隧道介绍 - Less Bug (less-bug.com),或者 --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers

成功后得到如下 join 命令(随机):

kubeadm join 10.0.0.100:6443 --token hr9m7r.i347n5rk1u144gws \
        --discovery-token-ca-cert-hash sha256:d26798c50f3560adfc21516c9337b7e6b0ebbbeced1a4a720f468ed66155ab8c
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

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

# 注意,这里需要一个网络插件
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 10.0.0.100:6443 --token hr9m7r.i347n5rk1u144gws \
        --discovery-token-ca-cert-hash sha256:d26798c50f3560adfc21516c9337b7e6b0ebbbeced1a4a720f468ed66155ab8c

如果 token 忘了,执行 kubeadm token create --print-join-command 重建

Work Node 加入

mkdir /var/lib/kubelet/

cat > /var/lib/kubelet/config.yaml <<EOF
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF

查看集群状态

export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl get nodes
NAME           STATUS     ROLES                  AGE     VERSION
dev-learn001   NotReady   control-plane,master   13m     v1.23.1
dev-learn002   NotReady   <none>                 4m47s   v1.23.1
dev-learn003   NotReady   <none>                 3m44s   v1.23.1
dev-learn004   NotReady   <none>                 3m42s   v1.23.1

NotReady 是因为网络未就绪,节点间无法互通。可以配置 Calico 网络。

安装网络插件

curl https://docs.projectcalico.org/manifests/calico.yaml -O

kubectl apply -f calico.yaml

网络配置好后就会变成 Ready

kubectl get nodes
NAME           STATUS   ROLES                  AGE   VERSION
dev-learn001   Ready    control-plane,master   24m   v1.23.1
dev-learn002   Ready    <none>                 16m   v1.23.1
dev-learn003   Ready    <none>                 15m   v1.23.1
dev-learn004   Ready    <none>                 15m   v1.23.1

k8s 常用命令

#查看集群所有节点
kubectl get nodes

#根据配置文件,给集群创建资源
kubectl apply -f xxxx.yaml

#查看集群部署了哪些应用?
docker ps   ===   kubectl get pods -A
# 运行中的应用在 docker 里面叫容器,在 k8s 里面叫 Pod
kubectl get pods -A

部署 Dashboard

https://github.com/kubernetes/dashboard

wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.4.0/aio/deploy/recommended.yaml

kubectl apply -f recommended.yaml

执行 kubectl get pods --all-namespaces 查看 pods 状态。

后续还要创建用户,配置 cert。参阅 https://github.com/kubernetes/dashboard/tree/master/docs

Troubleshott

官方排查例子

对 kubeadm 进行故障排查 | Kubernetes

Permissions for ‘private-key’ are too open

Windows SSH: Permissions for ‘private-key’ are too open

hostname “node” could not be reached

    [WARNING Hostname]: hostname "node" could not be reached
    [WARNING Hostname]: hostname "node": lookup node on 127.0.0.53:53: server misbehaving

注意配置 kubeadm-init.yaml 中的主节点名。

the number of available CPUs…

[ERROR NumCPU]: the number of available CPUs 1 is less than the required 2

CPU 至少双核。当然你也可以通过

--ignore-preflight-errors=NumCPU

参数来跳过,毕竟咱测试环境。

如果出错,记得 kubeadm reset 再重试

It seems like the kubelet isn’t running or healthy.、

journalctl -xeu kubelet 看日志。比如我遇到的是 failed to run Kubelet: misconfiguration: kubelet cgroup driver: “cgroupfs” is different from docker cgroup driver: “systemd”

我的解决:

cat > /etc/docker/daemon.json <<EOF
{
    "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
    "log-opts": {
    "max-size": "100m"
    },
    "storage-driver": "overlay2",
    "registry-mirrors":[
        "https://kfwkfulq.mirror.aliyuncs.com",
        "https://2lqq34jg.mirror.aliyuncs.com",
        "https://pee6w651.mirror.aliyuncs.com",
        "http://hub-mirror.c.163.com",
        "https://docker.mirrors.ustc.edu.cn",
        "https://registry.docker-cn.com"
    ]
}

修改 kubelet:

mkdir /var/lib/kubelet
cat > /var/lib/kubelet/config.yaml <<EOF
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF

重启 docker 与 kubelet:

systemctl daemon-reload
systemctl restart docker
systemctl restart kubelet

之后依然会产生错误日志,但 service kubelet status 显示 Running 就行。

Unable to update cni config: No networks found in /etc/cni/net.d

配置网络。上面讲了。

总结

再往后……

接下来需要的是实战。把服务部署到云。可以关注后续文章。

真的需要 k8s?

容器化带来灵活性,但也可能造成性能损失。据说 Hadoop 部署到 k8s 会导致 30% 性能损失。主要原因是容器虚拟网络的开销,可以通过 Host-Only 解决。

其它想法

这次部署花掉了我整整一个下午的时间。而且我发现里面还有大量自己不清楚的内容。所以,很多事情还是得交给专业的运维工程师。运维也并不简单。

参考

Docker 与 k8s 的恩怨情仇(一)— 成为 PaaS 前浪的 Cloud Foundry - 葡萄城技术团队 - 博客园 (cnblogs.com)

https://www.yuque.com/leifengyang/oncloud/ghnb83#AGHOX

自建 K8S 集群迁移 ACK 弹性裸金属集群 - 最佳实践 - 阿里云 (aliyun.com)

序言・Kubernetes Handbook - Kubernetes 中文指南 / 云原生应用架构实践手册・Jimmy Song