2.8 kubeadm 搭建

2.8 kubeadm 搭建 #

kubeadm 和 minikube 类似,也是用容器和镜像来封装 Kubernetes 的各种组件,但它的目标不是单机部署,而是要能够轻松地在集群环境里部署 Kubernetes,并且让这个集群接近甚至达到生产级别质量。

kubeadm 具有了和 minikube 一样的易用性,只要很少的几条命令,如 init、join、upgrade、reset 就能够完成 Kubernetes 集群的管理维护工作,让它不仅适用于集群管理员,也适用于开发、测试人员。

2.8.1 准备工作 #

所谓的多节点集群,要求服务器应该有两台或者更多,其实最小可用的 Kubernetes 集群就只有两台主机,一台是 Master 节点,另一台是 Worker 节点。Master 节点需要运行 apiserver、etcd、scheduler、controller-manager 等组件,管理整个集群,Worker 节点只运行业务应用。

因为 Kubernetes 对系统有一些特殊要求,所以要先在 Master 和 Worker 节点上做一些准备,包括改主机名、改 Docker 配置、改网络设置、改交换分区这四步。

第一,由于 Kubernetes 使用主机名来区分集群里的节点,所以每个节点的 hostname 必须不能重名。需要修改 /etc/hostname 这个文件,把它改成容易辨识的名字,比如 Master 节点就叫 master,Worker 节点就叫 worker。

第二,虽然 Kubernetes 目前支持多种容器运行时,但 Docker 还是最方便最易用的一种,所以使用 Docker 作为 Kubernetes 的底层支持,这里可以参考 Docker 官网 安装 Docker Engine。安装完成后需要再对 Docker 的配置做一点修改,在 /etc/docker/daemon.json 里把 cgroup 的驱动程序改成 systemd ,然后重启 Docker 的守护进程,具体的操作如下:

cat <<EOF | sudo tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

sudo systemctl enable docker
sudo systemctl daemon-reload
sudo systemctl restart docker

在 Docker 中,cgroup 驱动程序是用来管理和限制容器资源的一种机制。默认情况下,Docker 使用 cgroupfs 作为 cgroup 驱动程序。然而,将 Docker 的 cgroup 驱动程序更改为 systemd 可以带来一些好处:

  1. 与系统一致性:将 Docker 的 cgroup 驱动程序设置为 systemd 可以使 Docker 与系统中的其他进程一致。这样,Docker 容器的资源管理将与其他系统服务使用的 cgroup 驱动程序一致,简化了资源管理和监控。

  2. 安全性和隔离性:systemd-cgroups 提供了更强大的资源隔离和安全性特性。使用 systemd 作为 cgroup 驱动程序可以更好地利用 systemd 提供的资源控制和隔离功能,进一步增强容器的安全性和资源限制能力。

  3. 性能改进:systemd-cgroups 在某些情况下可能会提供更好的性能。systemd-cgroups 使用一个单一的 cgroup 作为容器的父级 cgroup,而 cgroupfs 则在每个层级都使用单独的 cgroup。这种改变可能会减少 cgroup 操作的数量,从而提高性能。

需要注意的是,将 Docker 的 cgroup 驱动程序更改为 systemd 需要系统已经安装和配置了 systemd,并且与 Docker 版本兼容。

第三,为了让 Kubernetes 能够检查、转发网络流量,你需要修改 iptables 的配置,启用 br_netfilter 模块:

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward=1 # better than modify /etc/sysctl.conf
EOF

sudo sysctl --system

br_netfilter 是 Linux 内核中的一个模块,用于在 Linux 桥接(bridge)网络中实现网络过滤功能。

Linux 桥接是一种网络技术,用于将多个网络接口连接在一起,形成一个逻辑上的网络。桥接器可以根据 MAC 地址来转发网络数据包,使得连接在不同网桥接口上的设备可以互相通信。

br_netfilter 模块提供了桥接网络中的网络过滤功能,主要用于实现网络层(IP)和传输层(TCP、UDP)的数据包过滤。具体来说,br_netfilter 模块使得可以在 Linux 桥接器上执行以下操作:

  1. 桥接器上的 IP 数据包过滤:通过在桥接器上启用 iptables 规则,可以对进出桥接器的 IP 数据包进行过滤、修改和重定向操作。这对于实施网络安全策略、防火墙规则和网络地址转换(NAT)等非常有用。

  2. 桥接器上的传输层数据包过滤:通过在桥接器上启用 ebtables 规则,可以对进出桥接器的传输层(如 TCP、UDP)数据包进行过滤、修改和重定向操作。这使得可以在桥接器级别上执行更细粒度的网络流量控制。

br_netfilter 模块扩展了 Linux 桥接网络的功能,使得可以在桥接器上实现更多的网络过滤和控制策略,提高网络的安全性和可配置性。

第四,你需要修改 /etc/fstab ,关闭 Linux 的 swap 分区,提升 Kubernetes 的性能:

sudo swapoff -a
sudo sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab

完成之后,重启一下系统。

2.8.2 安装 kubeadm #

在 Master 节点和 Worker 节点上都要做有安装 kubeadm 这一操作。kubeadm 可以直接从 Google 自己的软件仓库下载安装,但国内的网络不稳定,很难下载成功,需要改用其他的软件源,可以选择国内的某云厂商:

sudo apt install -y apt-transport-https ca-certificates curl

curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -

cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

sudo apt update

更新了软件仓库之后,可以用 apt install 获取 kubeadm、kubelet 和 kubectl 这三个安装必备工具。apt 默认会下载最新版本,也可以指定版本号,比如 1.23.3:

sudo apt install -y kubeadm=1.23.3-00 kubelet=1.23.3-00 kubectl=1.23.3-00

安装完成之后,可以用 kubeadm version、kubectl version 来验证版本是否正确:

kubeadm version
kubectl version --client

最后可以使用命令 apt-mark hold ,锁定这三个软件的版本,避免意外升级导致版本错误:

sudo apt-mark hold kubeadm kubelet kubectl

2.8.3 下载 Kubernetes 组件镜像 #

kubeadm 把 apiserver、etcd、scheduler 等组件都打包成了镜像,以容器的方式启动 Kubernetes,但这些镜像不是放在 Docker Hub 上,而是放在 Google 自己的镜像仓库网站 gcr.io,在国内的访问很困难。可以采取一些变通措施,提前把镜像下载到本地。使用命令 kubeadm config images list 可以查看安装 Kubernetes 所需的镜像列表,参数 --kubernetes-version 可以指定版本号:

kubeadm config images list --kubernetes-version v1.23.3

k8s.gcr.io/kube-apiserver:v1.23.3
k8s.gcr.io/kube-controller-manager:v1.23.3
k8s.gcr.io/kube-scheduler:v1.23.3
k8s.gcr.io/kube-proxy:v1.23.3
k8s.gcr.io/pause:3.6
k8s.gcr.io/etcd:3.5.1-0
k8s.gcr.io/coredns/coredns:v1.8.6

可以从国内的镜像网站下载然后再用 docker tag 改名,shell 脚本如下:

repo=registry.aliyuncs.com/google_containers

for name in `kubeadm config images list --kubernetes-version v1.23.3`; do

    src_name=${name#k8s.gcr.io/}
    src_name=${src_name#coredns/}

    docker pull $repo/$src_name

    docker tag $repo/$src_name $name
    docker rmi $repo/$src_name
done

2.8.4 安装 Master 节点 #

只需要一个命令 kubeadm init 就可以把组件在 Master 节点上运行起来,不过它还有很多参数用来调整集群的配置,可以使用 -h 查看。常见的有 3 个参数:

  • --pod-network-cidr,设置集群里 Pod 的 IP 地址段。

  • --apiserver-advertise-address,设置 apiserver 的 IP 地址,对于多网卡服务器来说很重要(比如 VirtualBox 虚拟机就用了两块网卡),可以指定 apiserver 在哪个网卡上对外提供服务。

  • --kubernetes-version,指定 Kubernetes 的版本号。下面的这个安装命令里,指定了 Pod 的地址段是10.10.0.0/16,apiserver 的服务地址是 192.168.10.210(主机的 IP 地址),Kubernetes 的版本号是 1.23.3。

sudo kubeadm init \
    --pod-network-cidr=10.10.0.0/16 \
    --apiserver-advertise-address=192.168.10.210 \
    --kubernetes-version=v1.23.3

kubeadm 安装完成后,会提示出接下来要做的工作:

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

另外还有一个很重要的 kubeadm join 提示,其他节点要加入集群必须要用指令里的 token 和 ca 证书,所以这条命令务必拷贝后保存好

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

kubeadm join 192.168.10.210:6443 --token tv9mkx.tw7it9vphe158e74 \
  --discovery-token-ca-cert-hash sha256:e8721b8630d5b562e23c010c70559a6d3084f629abad6a2920e87855f8fb96f3

安装 Flannel 网络插件 #

CNI(Container Networking Interface)标准是一个用于容器网络的开放标准和接口规范。它定义了容器运行时(如 Docker、Kubernetes 等)与网络插件之间的通信协议和数据格式,以实现容器网络的配置和管理。

CNI 标准的设计目标是提供一个统一的、可互操作的容器网络接口,使得不同的容器运行时可以使用各种网络插件,并与宿主机上的网络配置进行无缝集成。通过遵循 CNI 标准,容器运行时可以动态地创建、配置和删除容器网络,而不依赖于特定的容器运行时或网络插件实现。

Kubernetes 定义了 CNI 标准,有很多网络插件,可以使用常用的 Flannel。只需要使用如下的 kube-flannel.yml 文件(或者去仓库 https://github.com/flannel-io/flannel/)找相关文档)在 Kubernetes 里部署一下就好了。因为它应用了 Kubernetes 的网段地址,需要修改 net-conf.json 字段,把 Network 改成刚才 kubeadm 的参数 --pod-network-cidr 设置的地址段。比如在这里,就要修改成 10.10.0.0/16 :

apiVersion: v1
kind: Namespace
metadata:
  labels:
    k8s-app: flannel
    pod-security.kubernetes.io/enforce: privileged
  name: kube-flannel
---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: flannel
  name: flannel
  namespace: kube-flannel
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    k8s-app: flannel
  name: flannel
rules:
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes/status
    verbs:
      - patch
  - apiGroups:
      - networking.k8s.io
    resources:
      - clustercidrs
    verbs:
      - list
      - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    k8s-app: flannel
  name: flannel
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flannel
subjects:
  - kind: ServiceAccount
    name: flannel
    namespace: kube-flannel
---
apiVersion: v1
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "hairpinMode": true,
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }    
  net-conf.json: |
    {
      "Network": "10.10.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }    
kind: ConfigMap
metadata:
  labels:
    app: flannel
    k8s-app: flannel
    tier: node
  name: kube-flannel-cfg
  namespace: kube-flannel
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    app: flannel
    k8s-app: flannel
    tier: node
  name: kube-flannel-ds
  namespace: kube-flannel
spec:
  selector:
    matchLabels:
      app: flannel
      k8s-app: flannel
  template:
    metadata:
      labels:
        app: flannel
        k8s-app: flannel
        tier: node
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
      containers:
        - args:
            - --ip-masq
            - --kube-subnet-mgr
          command:
            - /opt/bin/flanneld
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: EVENT_QUEUE_DEPTH
              value: "5000"
          image: docker.io/flannel/flannel:v0.22.0
          name: kube-flannel
          resources:
            requests:
              cpu: 100m
              memory: 50Mi
          securityContext:
            capabilities:
              add:
                - NET_ADMIN
                - NET_RAW
            privileged: false
          volumeMounts:
            - mountPath: /run/flannel
              name: run
            - mountPath: /etc/kube-flannel/
              name: flannel-cfg
            - mountPath: /run/xtables.lock
              name: xtables-lock
      hostNetwork: true
      initContainers:
        - args:
            - -f
            - /flannel
            - /opt/cni/bin/flannel
          command:
            - cp
          image: docker.io/flannel/flannel-cni-plugin:v1.1.2
          name: install-cni-plugin
          volumeMounts:
            - mountPath: /opt/cni/bin
              name: cni-plugin
        - args:
            - -f
            - /etc/kube-flannel/cni-conf.json
            - /etc/cni/net.d/10-flannel.conflist
          command:
            - cp
          image: docker.io/flannel/flannel:v0.22.0
          name: install-cni
          volumeMounts:
            - mountPath: /etc/cni/net.d
              name: cni
            - mountPath: /etc/kube-flannel/
              name: flannel-cfg
      priorityClassName: system-node-critical
      serviceAccountName: flannel
      tolerations:
        - effect: NoSchedule
          operator: Exists
      volumes:
        - hostPath:
            path: /run/flannel
          name: run
        - hostPath:
            path: /opt/cni/bin
          name: cni-plugin
        - hostPath:
            path: /etc/cni/net.d
          name: cni
        - configMap:
            name: kube-flannel-cfg
          name: flannel-cfg
        - hostPath:
            path: /run/xtables.lock
            type: FileOrCreate
          name: xtables-lock

改好后,可以用 kubectl apply 来安装 Flannel 网络:

kubectl apply -f kube-flannel.yml

安装完成后,如果执行 kubectl get node 能够看到 Master 节点的状态是 Ready,则表明节点网络工作正常了。

2.8.5 安装 Worker 节点 #

如果已经成功安装了 Master 节点,那么 Worker 节点就执行执行之前拷贝的那条 kubeadm join 命令就可以了:

sudo kubeadm join 192.168.10.210:6443 --token tv9mkx.tw7it9vphe158e74 \
  --discovery-token-ca-cert-hash sha256:e8721b8630d5b562e23c010c70559a6d3084f629abad6a2920e87855f8fb96f3

kubeadm join 命令里的 token 有时效性,默认是 24 小时,如果失效了可以用 kubeadm token create 创建一个新的 token。

这条命令会连接 Master 节点,然后拉取镜像,安装网络插件,最后把节点加入集群。在这个过程中可能也会遇到拉取镜像的问题,可以按照安装 Master 的方式解决。Worker 节点安装完毕后,执行 kubectl get node ,就会看到两个节点都是 Ready 状态:


在 Master 和 Worker 都安装成功后,可以使用 kubectl run ,运行 Nginx 测试一下:

kubectl run ngx --image=nginx:alpine
kubectl get pod -o wide

2.8.6 Console 节点 #

在生产环境中,在 Kubernetes 集群之外还需要有一台起辅助作用的服务器,也就是 Console 控制台,Console 服务器上只需要安装一个 kubectl,所有对 Kubernetes 集群的管理命令都是从这台主机发出去的,因为出于因为安全的原因,集群里的主机部署好之后应该尽量少直接登录上去操作。

由于 Console 节点的部署只需要安装一个 kubectl,然后复制 config 文件就行,可以直接在 Master 节点上用 scp 远程拷贝,例如:

scp $(which kubectl) root@192.168.10.208:~/
scp ~/.kube/config root@192.168.10.208:~/.kube