K8s 部署指南

快速入门

通过 Minikube 快速体验 Kubernetes 系统。

系统安装

要部署 Kubenetes 系统,操作系统优先选择 CentOS 7 和 Ubuntu Server 版本。下面简单叙述系统安装流程。

安装 CentOS

先按照以下步骤来制作 CentOS 启动 U 盘:

  1. 网易镜像站点下载 CentOS 7.9 x64 镜像文件 CentOS-7-x86_64-DVD-2009.iso。
  2. GitHub 上下载 CentOS 启动 U 盘制作工具。下载完成后,安装并打开 Fedora Media Writer。
  3. 在 Fedora Media Writer 中,选择自定义镜像,并选择要写入的 U 盘。
  4. 使用 Fedora Media Writer 或其他 U 盘制作工具重新初始化 U 盘,使其成为 CentOS 启动 U 盘。

再按照下面步骤进行 CentOS 的安装:

  1. 将 U 盘插入主机,并启动进入 BIOS 设置界面。选择 UEFI 优先作为引导方式,并将 U 盘设置为第一引导设备。
  2. 在安装选择界面上选择 “Install CentOS 7”。
  3. 在安装信息摘要页面上,选择系统安装位置并进行硬盘分区:
    • 选择手动配置分区,然后点击 “完成”。
    • 点击 “自动创建挂载点”,然后删除 /homeswap 挂载点,并将剩余容量添加到根目录 / 下。
    • 点击 “开始安装”。
  4. 设置 root 密码,并等待安装完成。

安装 Ubuntu

Ubuntu 有分为 Desktop 和 Server 两个版本,其中 Server 版本对硬件兼容性较好且更新支持时间长。如果硬件兼容性较差,或者需要图形界面,可以选择 Desktop 版本。

安装 Ubuntu 22.04.2 Server 的步骤:

  1. 首先从官网下载 Ubuntu 22.04.2 Server 镜像,并使用 balenaEtcher 将其烧录到 U 盘。

  2. 在安装 Ubuntu Server 版本时,分区步骤非常复杂。如果不打算安装双系统,建议在 PE 环境下使用 DiskGenius 工具删除目标磁盘上的所有分区,然后再进行安装。

  3. 安装时大概率会碰到显卡驱动缺失问题,安装时或安装后可能会出现黑屏情况。可以在 grub 启动界面按下 e 键进行编辑,在 Linux 那行的末尾添加参数 nomodeset,然后按下 Ctrl+X 进行启动。

  4. 成功启动系统后,可以通过远程连接的方式修改 grub 配置。在 /etc/default/grub 文件中找到 GRUB_CMDLINE_LINUX_DEFAULT 。在后面添加参数 nomodeset,然后更新 grub:

    sudo vi /etc/default/grub
    sudo update-grub

    这样,在启动系统后就不会再出现黑屏问题了。

  5. 在安装过程中,对于没有明确说明的选项,一律选择 donecontinue 进入下一步。安装步骤如下:

    • 用 U 盘引导启动后,选择安装系统。
    • 修改网卡的 IP 为静态,配置好 IP 地址。
    • 将系统安装到整个磁盘上,并将挂载点 / 的大小调整为最大,文件系统格式选择 xfs。
    • 配置用户名为 assassing ,主机名为 ubuntu22
    • 选择安装 SSH 服务。
    • 等待安装完成后重新启动系统。

虚拟机安装

如果在虚拟机进行系统安装,可以省略掉制作启动 U 盘的步骤,其他步骤基本一致。

在网络配置方面,可以使用桥接模式,让虚拟机同宿主机在一个子网下,虚拟机无需特别设置即可访问外网和局域网内其他主机。但要注意的是,国内网络运营商在光猫上对连接的客户端数量有限制,连接超过 5 个设备会随机选取设备断网。如果同时开了很多台虚拟机,会出现部分虚拟机网络不正常,部分又正常的情况。

行之有效的解决方法有三个:

  • 找运营商上门技术支持,将限制去除。或者用自己的设备替代运营商送的光猫,作为拨号连接设备。这可能需要很多扯皮。
  • 在光猫后接一个自己的路由器,客户端都接到路由器上。路由器和光猫不是一个网段,在光猫上只能看到路由器一个客户端,自然不存在限制。
  • 不使用桥接网络模式,采用 NAT 模式来转发虚拟机流量。

如果采用 NAT 网络模式,还需要对网络进行手动配置。下面以配置成 192.168.1.0.24 网段为例:

  • 将 NAT 网段设为 192.168.1.0/24,网关设为 192.168.1.1。
  • 将 NAT 虚拟网卡(VM 中默认名 VMnet8)的 IP 设为 192.168.1.100,子网掩码 255.255.255.0,网关地址 192.168.1.1。
  • 将能通外网的真实网卡属性配置成共享式,选择 NAT 虚拟网卡,这样虚拟机也能连通外网。
  • 还可以配置 NAT 端口转发,例如将虚拟机的端口 22 转发到宿主机 10101 端口上,这样外部服务器可以用 ssh 通过宿主机 IP 地址加上 10101 端口连接虚拟机。

Docker 基础

首先需要在主机上安装好 Docker,然后创建一个简单的 Node.js 镜像并运行。

安装 Docker

安装 Docker-CE 20.10.10 版本参考步骤如下:

[root@k8s-101 ~]# yum install -y yum-utils
[root@k8s-101 ~]# yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@k8s-101 ~]# yum makecache fast
[root@k8s-101 ~]# yum install -y docker-ce-20.10.10 docker-ce-cli-20.10.10 containerd.io docker-compose

在 Ubuntu 中安装命令如下:

root@ubuntu22:~# curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
root@ubuntu22:~# add-apt-repository -y "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
root@ubuntu22:~# apt install -y docker-ce=5:20.10.17~3-0~ubuntu-jammy docker-ce-cli=5:20.10.17~3-0~ubuntu-jammy containerd.io docker-compose

配置 Docker

通过新增配置文件 /etc/docker/daemon.json 来设置 Docker 参数:

[root@k8s-101 ~]# mkdir /etc/docker/ ; tee /etc/docker/daemon.json <<-'EOF'
{
  "exec-opts": [
      "native.cgroupdriver=systemd"
      ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "registry-mirrors": [
      "http://192.168.1.253:10007",
      "https://docker.nju.edu.cn",
      "https://registry.cn-hangzhou.aliyuncs.com",
      "https://registry.cn-shanghai.aliyuncs.com",
      "https://registry.cn-qingdao.aliyuncs.com",
      "https://registry.cn-beijing.aliyuncs.com",
      "https://registry.cn-zhangjiakou.aliyuncs.com",
      "https://registry.cn-huhehaote.aliyuncs.com",
      "https://registry.cn-wulanchabu.aliyuncs.com",
      "https://registry.cn-shenzhen.aliyuncs.com",
      "https://registry.cn-heyuan.aliyuncs.com",
      "https://registry.cn-guangzhou.aliyuncs.com",
      "https://registry.cn-chengdu.aliyuncs.com",
      "https://mirror.ccs.tencentyun.com",
      "https://dockerhub.azk8s.com",
      "https://hub-mirror.c.163.com",
      "https://mirror.sjtu.edu.cn",
      "https://f1361db2.m.daocloud.io",
      "https://registry.cn-hongkong.aliyuncs.com",
      "https://registry.ap-northeast-1.aliyuncs.com",
      "https://registry.ap-southeast-1.aliyuncs.com",
      "https://registry.ap-southeast-2.aliyuncs.com",
      "https://registry.ap-southeast-3.aliyuncs.com",
      "https://registry.ap-southeast-5.aliyuncs.com",
      "https://registry.ap-south-1.aliyuncs.com",
      "https://registry.eu-central-1.aliyuncs.com",
      "https://registry.eu-west-1.aliyuncs.com",
      "https://registry.us-west-1.aliyuncs.com",
      "https://registry.us-east-1.aliyuncs.com",
      "https://registry.me-east-1.aliyuncs.com"
      ],
  "insecure-registries": [
      "192.168.1.253:10007",
      "192.168.1.253:10008"
      ]
}
EOF

配置参数的解释如下:

  • exec-opts:设置 cgroup 驱动方式,与 Kubernetes 中 kubelet 使用的方式匹配。
  • log-driverlog-opts:设置 Docker 容器的日志格式和限制,日志文件最大为 10M,最多保留 3 个文件,以避免容器日志过大占用硬盘空间。
  • registry-mirrors:镜像仓库地址。Docker 官方仓库地址已被屏蔽,不得已加入一大堆备用镜像仓库地址。
  • insecure-registries:设置私有仓库地址,忽略对强制 HTTPS 认证的要求。

启动 Docker

配置完毕后,启动并设置 Docker 自启动:

[root@k8s-101 ~]# systemctl daemon-reload
[root@k8s-101 ~]# systemctl enable --now docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
[root@k8s-101 ~]# docker -v
Docker version 20.10.10, build b485636
[root@k8s-101 ~]# docker-compose -v
docker-compose version 1.18.0, build 8dd22a9

创建 Node.js 应用

创建一个简单的 Node.js Web 应用,并打包成容器镜像。应用会接受 HTTP 请求并响应应用运行的主机名。

新建一个 app.js 文件,内容如下:

[root@centos7 ~]# vi app.js
const http = require('http');
const os = require('os');

console.log("Runing...");

var handle = function(request, response){
        console.log("Request IP: " + request.connection.remoteAddress);
        response.writeHead(200);
        response.end("Hostname: " + os.hostname() + "\n");
};

var www = http.createServer(handle);
www.listen(8080);

应用在 8080 端口启动一个 HTTP 服务器,服务器会以状态码 200 和输出消息来响应每个请求。请求处理程序会将客户端 IP 打印到标准输出。

创建 Dockerfile

Dockerfile 文件需要和 app.js 文件放在同一目录,内容如下:

[root@centos7 ~]# vi Dockerfile
FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]

这里使用 Node 7 版本的基础镜像,然后将 app.js 文件添加到镜像根目录,最后执行 node app.js 命令。

构建容器镜像

构建不是由 Docker 客户端进行的,而是将整个目录的文件上传到 Docker 守护进程并在那里进行。因此,在守护进程运行在另外一个服务器时,不要在构建目录中包含不需要的文件。

使用 docker build 构建名为 kubia 的镜像:

[root@centos7 ~]# docker build -t kubia .
Sending build context to Docker daemon  14.34kB
Step 1/3 : FROM node:7
 ---> d9aed20b68a4
Step 2/3 : ADD app.js /app.js
 ---> aa745953f3ae
Step 3/3 : ENTRYPOINT ["node", "app.js"]
 ---> Running in 76a3951dc11c
Removing intermediate container 76a3951dc11c
 ---> cdd0c7614e03
Successfully built cdd0c7614e03
Successfully tagged kubia:latest

Dockerfile 中每一条指令都会创建一个新层。上面的例子中,有一层用来添加 app.js,另外一层运行 node app.js 命令,最后一层会被标记为 kubia:latest。构建完成后镜像会存储在本地。

[root@centos7 ~]# docker images kubia
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
kubia        latest    cdd0c7614e03   30 seconds ago   660MB

运行容器镜像

指定容器名为 kubia-container,暴露端口 8080,在后台运行:

[root@centos7 ~]# docker run --name kubia-container -p 8080:8080 -d kubia
c971ba30949f2b1dbca90a96274a3a06a577d33f981e9bc82f327fbdb7010277

启动后在浏览器通过 8080 端口访问服务:

[root@centos7 ~]# curl 127.0.0.1:8080
Hostname: f4c9088f3b28

此时容器主机名就是 Docker 容器短 ID。可以查询容器日志:

[root@centos7 ~]# docker logs kubia-container
Runing...
Request IP: ::ffff:192.168.2.101
Request IP: ::ffff:192.168.2.205

搭建 Minikube

Minikube 是一个构建单节点集群的工具,具体使用可以参考:GitHub

安装

下面通过下载 RPM 包的方式安装 Minikube:

[root@centos7 ~]# curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-latest.x86_64.rpm
[root@centos7 ~]# rpm -Uvh minikube-latest.x86_64.rpm

还需要安装 Kubectl 客户端来与 Minikube 进行交互:

[root@centos7 ~]# curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
[root@centos7 ~]# chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl

启动

如果不带参数启动,会报错说不能使用 root 权限运行。这里使用一个加入了 docker 组的 user1 来运行。

另外经常会碰到镜像拉取失败的情况,可以加入 --image-mirror-country=cn --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers 参数指定镜像仓库:

[root@server6 ~]$ useradd -G docker user1
[root@server6 ~]$ su -l user1
[user1@server6 ~]$ minikube start
* minikube v1.23.2 on Centos 7.9.2009
* Automatically selected the docker driver
* Using image repository registry.cn-hangzhou.aliyuncs.com/google_containers
* Starting control plane node minikube in cluster minikube
* Pulling base image ...
* Creating docker container (CPUs=2, Memory=2200MB) ...
* Verifying Kubernetes components...
* Enabled addons: storage-provisioner, default-storageclass
* kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
* Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

遇到其他报错可以删除文件后,重新运行启动命令:

[user1@server6 ~]$ minikube delete
* Deleting "minikube" in docker ...
* Removed all traces of the "minikube" cluster.
[user1@server6 ~]$ rm -rf .minikube

查看

安装好以后可以使用 kubectl 命令查看集群工作状态:

[user1@server6 ~]$ kubectl cluster-info 
Kubernetes control plane is running at https://192.168.49.2:8443
CoreDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
[user1@server6 ~]$ kubectl get nodes
NAME       STATUS   ROLES                  AGE     VERSION
minikube   Ready    control-plane,master   8m40s   v1.22.2
[user1@server6 ~]$ kubectl describe node minikube 
Name:               minikube
Roles:              control-plane,master
...

建立 Pod

使用 kubectl run 命令可以创建所有必要组件而无需使用 JSON 或 YAML 文件。其中使用 --image 指定镜像,使用 --port 说明容器监听端口:

[user1@server6 ~]$ kubectl run kubia --image=assassing/kubia --port=8080
pod/kubia created

不能用 kubectl 直接列出容器,因为 Pod 才是操作对象。一个 Pod 包含一个或多个容器,每个容器都运行一个应用进程,它们总是运行在同一个工作节点及命名空间中。查看 Pod 运行状态:

[user1@server6 ~]$ kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
kubia   1/1     Running   0          58s

暴露端口

每个 Pod 都有自己的 IP,Pod 分布在不同的工作节点上。它们不能被外部访问,需要通过服务对象来公开:

[user1@server6 ~]$ kubectl expose pod kubia --type=NodePort 
service/kubia exposed
[user1@server6 ~]$ minikube service kubia --url
http://192.168.49.2:32556
[user1@server6 ~]$ kubectl expose pod kubia --type=LoadBalancer --name kubia-http
service/kubia-http exposed

查看刚刚新建的服务:

[user1@server6 ~]$ kubectl get services
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP          42m
kubia        NodePort       10.107.120.56   <none>        8080:32556/TCP   3m22s
kubia-http   LoadBalancer   10.108.108.14   <pending>     8080:31003/TCP   56s

显示 pending 是因为 Minikube 不支持 LoadBalancer 类型的服务,可以用另外一个命令查看:

[user1@server6 ~]$ minikube service kubia-http
|-----------|------------|-------------|---------------------------|
| NAMESPACE |    NAME    | TARGET PORT |            URL            |
|-----------|------------|-------------|---------------------------|
| default   | kubia-http |        8080 | http://192.168.49.2:31003 |
|-----------|------------|-------------|---------------------------|
* Opening service default/kubia-http in default browser...

切换回 root 账号,试着访问一下:

[root@centos7 ~]# curl http://192.168.49.2:31401
Hostname: kubia
[root@centos7 ~]# curl http://192.168.49.2:32050
Hostname: kubia

系统配置

主要以 CentOS 7 系统作为例子。其他发行版在安装工具步骤和自带工具使用上可能有差别,只要达成目标即可。

修改 IP 地址

安装完系统之后,第一件事就是修改好 IP 地址,然后通过 ssh 远程来连接系统。

CentOS

要修改 IP 地址可通过 nmcli 命令,或者修改 /etc/sysconfig/network-scripts/ 下面的网卡配置文件都可:

[root@localhost ~]# nmcli connection modify ens32 connection.autoconnect yes ipv4.method manual ipv4.addresses 192.168.1.101/24 ipv4.gateway 192.168.1.1 ipv4.dns 8.8.8.8
[root@localhost ~]# nmcli connection up ens32
[root@localhost ~]# nmcli
ens32: connected to ens32
        "Intel 82545EM"
        ethernet (e1000), 00:0C:29:DB:DA:56, hw, mtu 1500
        ip4 default, ip6 default
        inet4 192.168.1.101/24
        route4 192.168.1.0/24
        route4 0.0.0.0/0
        inet6 240e:383:419:201:f417:1a97:df8f:9932/64
        inet6 fe80::5f5c:196b:8e66:a650/64
        route6 fe80::/64

上面修改网卡 ens32 的 IP 地址为 192.168.1.101,网关地址 192.168.1.1,DNS 地址 8.8.8.8。并立即生效。

Ubuntu

Ubuntu 中默认不带 nmcli ,可以通过修改配置文件 /etc/netplan/00-installer-config.yaml 来修改网卡配置:

assassing@ubuntu22:~$ sudo vi /etc/netplan/00-installer-config.yaml
assassing@ubuntu22:~$ sudo netplan apply

设置 DNS 服务器地址:

root@ubuntu22:~# sed -i "s/#DNS=/DNS=8.8.8.8 114.114.114.114/g" /etc/systemd/resolved.conf
root@ubuntu22:~# systemctl restart systemd-resolved

账户配置

如果对操作系统不熟练,不建议使用 root 账户作为日常用户。下面是配置使用 root 账户的方法。

阿里云

刚刚开通的阿里云服务器,无法通过 ssh 直接连接。可以通过阿里云管理平台,点击远程连接,选择发送命令来配置主机。

例如修改 ssh 默认端口由 22 改为 2222,并允许使用账号密码登录。重启 sshd 服务来生效:

#!/bin/bash
sed -i "s/#Port 22/Port 2222/g" /etc/ssh/sshd_config
sed -i "s/PasswordAuthentication no/PasswordAuthentication yes/g" /etc/ssh/sshd_config
systemctl restart sshd

通过发送脚本的方式修改 root 账号密码:

#!/bin/bash
echo "qv4YB7#x9WhPj?i4E1DRgu1dlOWR" | passwd root --stdin > /dev/null 2>&1

添加用户 alice 并设置密码。密码是 /etc/shadow 中哈希值:

#!/bin/bash
useradd -p \$6\$YVLnCcQL\$NlbdFMzHHzw9Byk2EFjl4BNCm0riq22HnJKTVcccZv/FQ/Y2HgvSeavPCUbdgzTmA3T3ksn7SYkq96An12oBW0 alice

最后要记得调整安全组策略。在云服务器 ECS 的安全组中,为自定义的 sshd 端口 2222 设置白名单,这样才能使用 ssh 远程连接。

Ubuntu

在 Ubuntu 中,默认情况下是没有开启 root 用户登录。首先需要使用普通用户登录,并设置 root 账号的密码:

assassing@ubuntu22:~$ sudo passwd root
New password: 
Retype new password: 
passwd: password updated successfully
assassing@ubuntu22:~$ su -l
Password: 
root@ubuntu22:~# 

然后,修改 SSH 服务的配置文件,允许 root 用户使用 SSH 连接:

root@ubuntu22:~# sed -i "s/#PermitRootLogin prohibit-password/PasswordAuthentication yes/g" /etc/ssh/sshd_config
root@ubuntu22:~# sed -i '85a\PermitRootLogin yes' /etc/ssh/sshd_config
root@ubuntu22:~# systemctl restart sshd

现在可以使用 root 用户直接连接到 22 端口,并删除普通用户 “assassing”:

root@ubuntu22:~# userdel -r assassing

登录限制

可以设置一台服务器作为跳板机,只能通过它连接其他服务器。

主节点

跳板机 ssh 连接端口是 2222,修改 sshd 配置来禁止 root 用户登录。只能通过 普通 用户登录:

[root@k8s-s1 ~]# sed -i "s/PermitRootLogin yes/PermitRootLogin no/g" /etc/ssh/sshd_config
[root@k8s-s1 ~]# systemctl restart sshd

安装 Fail2ban 来阻止恶意登录:

[root@k8s-m1-pro ~]# yum install -y fail2ban

修改配置启用通过 iptables 防护非法 sshd 登录。规则为在 300 秒内输错 5 次密码,IP 被禁止连接 2222 端口 1 个小时:

[root@k8s-m1-pro ~]# vi /etc/fail2ban/jail.conf
...
[sshd]

# To use more aggressive sshd modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all).
# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
#mode   = normal
enabled = true
filter = sshd
port    = 2222
action = iptables[name=SSH, port=2222, protocol=tcp]
logpath = %(sshd_log)s
backend = %(sshd_backend)s
bantime = 3600
findtime = 300
maxretry = 5
...

启动服务:

[root@k8s-m1-pro fail2ban]# systemctl enable --now fail2ban

如果需要限制 Nginx 访问可以参考:知乎专栏

其他节点

除了跳板机外,修改系统 /etc/hosts.deny/etc/hosts.allow 文件内容,只允许通过内网 ssh 连接,其他地址拒绝:

[root@k8s-w2-pro ~]# echo "sshd:ALL:deny" >> /etc/hosts.deny
[root@k8s-w2-pro ~]# echo "sshd:172.16.0.0/255.255.0.0:allow" >> /etc/hosts.allow
[root@k8s-w2-pro ~]# systemctl restart sshd

查询异常登录

/var/log/secure 文件记录有异常登录信息:

[root@k8s-m1-pro ~]# tail -n 30 /var/log/secure

在跳板机上可以通过 fail2ban-client 命令查看被 ban 列表:

[root@k8s-m1-pro fail2ban]# fail2ban-client status
[root@k8s-m1-pro fail2ban]# fail2ban-client status sshd
[root@k8s-m1-pro fail2ban]# iptables -nvL

解除被 ban 的 IP 地址操作:

[root@k8s-m1-pro fail2ban]# fail2ban-client set sshd unbanip 47.101.138.100

镜像仓库

修改 yum 仓库地址为阿里云镜像仓库,提升软件安装和更新速度:

[root@localhost ~]# yum install -y wget
[root@localhost ~]# mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
[root@localhost ~]# mv /etc/yum.repos.d/epel.repo /etc/yum.repos.d/epel.repo.backup
[root@localhost ~]# mv /etc/yum.repos.d/epel-testing.repo /etc/yum.repos.d/epel-testing.repo.backup
[root@localhost ~]# cd /etc/yum.repos.d/ && wget http://mirrors.aliyun.com/repo/Centos-7.repo && wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo && cd -
[root@localhost ~]# yum clean all
[root@localhost ~]# yum makecache

虽然阿里云镜像仓库下载速度被限制成 500 kB/s,但胜在稳定。

内核升级

CentOS 7 安装镜像自带的 3.10 内核已太旧,很多应用需要的内核功能支持不了。装好后第一时间升级内核。

查看内核

查看当前运行的内核版本:

[root@localhost ~]# uname -sr
Linux 3.10.0-1160.el7.x86_64

安装内核

导入 ELRepo 软件仓库的公共秘钥,安装 yum 源:

[root@localhost ~]# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
[root@localhost ~]# yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm

列出当前最新内核版本:

[root@localhost ~]# yum --disablerepo="*" --enablerepo="elrepo-kernel" list available
Available Packages
kernel-lt.x86_64    5.4.242-1.el7.elrepo   elrepo-kernel
kernel-ml-devel.x86 5.4.242-1.el7.elrepo   elrepo-kernel
kernel-ml.x86_64    6.3.2-1.el7.elrepo     elrepo-kernel

其中 kernel-lt 表示 long-term(长期支持版本),kernel-ml 表示 latest mainline(最新主线版本)。选择安装 kernel-lt.x86_64 版本。

[root@localhost ~]# yum install -y kernel-lt-5.4.242-1.el7.elrepo --enablerepo=elrepo-kernel

修改引导

查看已安装系统内核。正常情况下会保留旧内核,升级失败时可以回滚:

[root@localhost ~]# awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg
0 : CentOS Linux (5.4.242-1.el7.elrepo.x86_64) 7 (Core)
1 : CentOS Linux (3.10.0-1160.el7.x86_64) 7 (Core)
2 : CentOS Linux (0-rescue-e175a587657a4fae8ab45bb178e24c22) 7 (Core)

如果引导方式是 UEFI,则需要查看 /etc/grub2-efi.cfg 文件:

[root@localhost ~]# awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2-efi.cfg

设置内核启动顺序,需要重新生成引导文件:

[root@localhost ~]# grub2-set-default 0
[root@localhost ~]# grub2-mkconfig -o /boot/grub2/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.4.242-1.el7.elrepo.x86_64
Found initrd image: /boot/initramfs-5.4.242-1.el7.elrepo.x86_64.img
Found linux image: /boot/vmlinuz-3.10.0-1160.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-1160.el7.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-e175a587657a4fae8ab45bb178e24c22
Found initrd image: /boot/initramfs-0-rescue-e175a587657a4fae8ab45bb178e24c22.img
done

UEFI 启动配置文件位置的位置不同:

[root@localhost ~]# grub2-set-default 0
[root@localhost ~]# grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

降级内核

内核降级和升级步骤一样,用 yum 安装指定内核版本后,修改引导配置文件并重启。

删除内核

重启后如果新内核运作正常,可以删除旧内核:

[root@localhost ~]# yum remove -y kernel-lt-3.10.0-1160.el7.elrepo.x86_64

驱动问题

如果旧网卡驱动识别不了,需要手动载入驱动:

[root@k8s-250 ~]# rmmod r8169 && modprobe r8169 && systemctl restart network

设置为系统启动服务:

[root@k8s-250 /]# tee /etc/systemd/system/load-realtek-driver.service<<EOF
[Unit]
Description=Load Realtek drivers.
Before=network-online.target

[Service]
Type=simple
ExecStartPre=/usr/sbin/rmmod r8169
ExecStart=/usr/sbin/modprobe r8169

[Install]
WantedBy=multi-user.target
EOF
[root@k8s-250 /]# systemctl enable load-realtek-driver.service

内核功能调整

需要修改内核参数,否则安装运行 K8s 会失败.

禁用虚拟内存

虽然安装系统时已经去除了 swap 分区,但保险起见,还是对开机挂载配置文件 /etc/fstab 进行重写。然后重新挂载根目录:

[root@localhost ~]# swapoff -a
[root@localhost ~]# yes | cp /etc/fstab /etc/fstab_bak
[root@localhost ~]# cat /etc/fstab_bak |grep -v swap > /etc/fstab
[root@localhost ~]# mount -n -o remount,rw /
[root@localhost ~]# echo "vm.swappiness = 0" >> /etc/sysctl.conf

开启端口转发

首先要确认网卡 MAC 地址没有冲突,然后确保 br_netfilteroverlay 模块被加载:

[root@localhost ~]# echo "br_netfilter" >> /etc/modules-load.d/k8s.conf
[root@localhost ~]# echo "overlay" >> /etc/modules-load.d/k8s.conf
[root@localhost ~]# modprobe br_netfilter
[root@localhost ~]# modprobe overlay
[root@localhost ~]# lsmod | grep 'br_netfilter\|overlay'
br_netfilter           28672  0 
overlay               114688  35 
[root@localhost ~]# modprobe ip_vs
[root@localhost ~]# modprobe ip_vs_rr
[root@localhost ~]# modprobe ip_vs_wrr
[root@localhost ~]# modprobe ip_vs_sh
[root@localhost ~]# echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
[root@localhost ~]# echo "net.ipv4.ip_nonlocal_bind = 1" >> /etc/sysctl.conf
[root@localhost ~]# echo "net.ipv4.ip_local_port_range = 1024 65535" >> /etc/sysctl.conf
[root@localhost ~]# echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.conf
[root@localhost ~]# echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.conf
[root@localhost ~]# echo "net.core.rmem_max=2500000" >> /etc/sysctl.conf
[root@localhost ~]# echo "10000 65535" > /proc/sys/net/ipv4/ip_local_port_range

去除文件系统限制

根据硬件情况,对文件系统限制调大。最后使用 sysctl --system 命令来使配置生效:

[root@localhost ~]# ulimit -SHn 65536
[root@localhost ~]# echo "* soft nofile 655350" >> /etc/security/limits.conf
[root@localhost ~]# echo "* hard nofile 655350" >> /etc/security/limits.conf
[root@localhost ~]# echo "vm.max_map_count = 524288" >> /etc/sysctl.conf
[root@localhost ~]# echo "fs.file-max = 655350" >> /etc/sysctl.conf
[root@localhost ~]# echo "fs.inotify.max_user_instances=8192" >> /etc/sysctl.conf
[root@localhost ~]# sysctl --system

关闭防火墙

关闭防火墙和 SELinux,否则容器访问主机文件系统会不正常:

[root@localhost ~]# service iptables stop
[root@localhost ~]# chkconfig iptables off
[root@localhost ~]# systemctl stop firewalld
[root@localhost ~]# systemctl disable firewalld
[root@localhost ~]# setenforce 0
[root@localhost ~]# sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config
[root@localhost ~]# getenforce #

个性化设置

一些按照喜好来设置的系统调整。

修改系统语言

修改系统语言为英语:

[root@localhost ~]# echo 'LANG="en_US.UTF-8"' > /etc/locale.conf

修改主机名

修改节点上的主机名,例如 k8s-master-1、k8s-m1-pro、k8s-250 等,尽量取有意义的名字:

[root@localhost ~]# hostnamectl set-hostname k8s-101

将主机名添加到本地域名解析 /etc/hosts 文件中:

[root@localhost ~]# echo "127.0.0.1   $(hostname)" >> /etc/hosts

时间同步

设置网络时间同步。若服务器时间不对,可能会遇到一些莫名其妙的验证方面问题:

[root@localhost ~]# timedatectl set-timezone "Asia/Shanghai"
[root@localhost ~]# yum -y install ntp ntpdate
[root@localhost ~]# ntpdate cn.pool.ntp.org
[root@localhost ~]# hwclock --systohc
[root@localhost ~]# hwclock -w
[root@localhost ~]# date

安装工具

安装一些常用系统维护工具:

[root@localhost ~]# yum install -y git wget net-tools bind-utils vim bash-completion nfs-utils jq nc telnet lvm2 unzip iftop lsof

Ubuntu 中 bind-utils 替换为 bind9-utilsnfs-utils 替换为 nfs-kernel-servernc 已经自带。安装命令改为这样:

root@ubuntu22:~# apt-get update
root@ubuntu22:~# apt install -y git wget net-tools bind9-utils vim bash-completion jq telnet lvm2 nfs-kernel-server

限制日志大小

限制系统 journal 日志保留时常为 1 周,免得磁盘被大量日志挤占空间:

[root@localhost ~]# journalctl --vacuum-time=1w

设置变量

修改 /etc/profile 文件,添加一些常用变量:

[root@localhost ~]# echo "export HISTTIMEFORMAT='`whoami` : %F %T :'" >> /etc/profile
[root@localhost ~]# echo "export NFS_SERVER=192.168.1.253" >> /etc/profile

NFS 服务器地址变量按实际情况填写。

免密登录

可以在特定服务器上设置免密登录别的服务器:

[root@k8s-250 ~]# ssh-keygen
[root@k8s-250 ~]# ssh-copy-id root@192.168.1.248

编辑器配置

在 Ubuntu 中,vi 需要设置粘贴模式,禁止粘贴文本时,可能会出现自动缩进或添加额外的空格和制表符的情况:

root@ubuntu22:~# echo "set paste" >> /etc/vim/vimrc 

磁盘管理

一般建议把容器工作目录和数据储存同系统根目录分开挂载,免得影响系统运行稳定性。下面以新增一块储存盘为例,演示磁盘管理操作。

初始化

服务器上新增一块固态硬盘 sdb,需要初始化后才能使用:

[root@k8s-master ~]# lsblk
NAME            MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda               8:0    0 223.6G  0 disk 
├─sda1            8:1    0   200M  0 part /boot/efi
├─sda2            8:2    0     1G  0 part /boot
└─sda3            8:3    0 222.4G  0 part 
  └─centos-root 253:0    0 222.4G  0 lvm  /
sdb               8:16   0 931.5G  0 disk 

使用 fdisk 命令来管理磁盘:

[root@k8s-master ~]# fdisk /dev/sdb
Welcome to fdisk (util-linux 2.23.2).

Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table
Building a new DOS disklabel with disk identifier 0x50398e42.

Command (m for help): p

Disk /dev/sdb: 1000.2 GB, 1000204886016 bytes, 1953525168 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x50398e42

   Device Boot      Start         End      Blocks   Id  System

先使用初始化并分区:

Command (m for help): n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Partition number (1-4, default 1): 
First sector (2048-1953525167, default 2048): 
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-1953525167, default 1953525167): 
Using default value 1953525167
Partition 1 of type Linux and of size 931.5 GiB is set

修改分区 system IDlvm

Command (m for help): t
Selected partition 1
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'
Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.
[root@k8s-master ~]# partprobe

创建 PV

使用 pvcreate 命令来创建 PV:

[root@k8s-103 ~]# pvcreate /dev/sdb1
  Physical volume "/dev/sdb1" successfully created.
[root@k8s-103 ~]# pvscan
  PV /dev/sda2   VG centos          lvm2 [<49.00 GiB / 0    free]
  PV /dev/sdb1                      lvm2 [<200.00 GiB]
  Total: 2 [<249.00 GiB] / in use: 1 [<49.00 GiB] / in no VG: 1 [<200.00 GiB]

查询刚创建的 PV 详细信息:

[root@k8s-103 ~]# pvdisplay
  --- Physical volume ---
  PV Name               /dev/sda2
  VG Name               centos
  PV Size               <49.00 GiB / not usable 3.00 MiB
  Allocatable           yes (but full)
  PE Size               4.00 MiB
  Total PE              12543
  Free PE               0
  Allocated PE          12543
  PV UUID               FOgL5t-v6EE-neHA-Kdpy-ynt0-yzXr-Tin7Am
   
  "/dev/sdb1" is a new physical volume of "<200.00 GiB"
  --- NEW Physical volume ---
  PV Name               /dev/sdb1
  VG Name               
  PV Size               <200.00 GiB
  Allocatable           NO
  PE Size               0   
  Total PE              0
  Free PE               0
  Allocated PE          0
  PV UUID               rxkppt-GxSK-igDI-NgI1-3CZw-F3r2-YtXIU9

创建 VG

声明变量:

[root@k8s-103 ~]# export DIR=ssd

新建 VG:

[root@k8s-103 ~]# vgcreate ${DIR} /dev/sdb1
  Volume group "ssd" successfully created
[root@k8s-103 ~]# vgscan
  Reading volume groups from cache.
  Found volume group "centos" using metadata type lvm2
  Found volume group "ssd" using metadata type lvm2
[root@k8s-103 ~]# vgdisplay
  --- Volume group ---
  VG Name               centos
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  2
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                1
  Open LV               1
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               <49.00 GiB
  PE Size               4.00 MiB
  Total PE              12543
  Alloc PE / Size       12543 / <49.00 GiB
  Free  PE / Size       0 / 0   
  VG UUID               xuRCcF-ROBb-5OhK-4BAY-fuBi-EvlU-uqxs9T
   
  --- Volume group ---
  VG Name               ssd
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               <200.00 GiB
  PE Size               4.00 MiB
  Total PE              51199
  Alloc PE / Size       0 / 0   
  Free  PE / Size       51199 / <200.00 GiB
  VG UUID               yq6LIQ-D4Xm-hPGZ-2Zqi-Bdsc-vz3Y-cvuAmC

创建 LV

建立名为 ssd 的 LV,使用参数 -l 指定分配 free PE 数量:

[root@k8s-103 ~]# lvcreate -l +100%FREE -n ${DIR} ${DIR}
  Logical volume "ssd" created.
[root@k8s-103 ~]# lvdisplay
  --- Logical volume ---
  LV Path                /dev/centos/root
  LV Name                root
  VG Name                centos
  LV UUID                GySaQs-a1ni-kiUg-Ys86-j287-Sqpu-9PWECL
  LV Write Access        read/write
  LV Creation host, time localhost, 2023-05-17 08:02:19 +0800
  LV Status              available
  # open                 1
  LV Size                <49.00 GiB
  Current LE             12543
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     8192
  Block device           253:0
   
  --- Logical volume ---
  LV Path                /dev/ssd/ssd
  LV Name                ssd
  VG Name                ssd
  LV UUID                EkAIWn-ad01-3nsX-cfBN-rpgV-dSwk-Pc1MJI
  LV Write Access        read/write
  LV Creation host, time k8s-103, 2023-05-17 02:28:34 +0800
  LV Status              available
  # open                 0
  LV Size                <200.00 GiB
  Current LE             51199
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     8192
  Block device           253:1

格式化

格式化成 xfs 格式,并挂载到指定目录下面:

[root@k8s-103 ~]# mkfs.xfs /dev/${DIR}/${DIR}
meta-data=/dev/ssd/ssd           isize=512    agcount=4, agsize=13106944 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=0, sparse=0
data     =                       bsize=4096   blocks=52427776, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal log           bsize=4096   blocks=25599, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0         extsz=4096   blocks=0, rtextents=0
[root@k8s-103 ~]# mkdir /${DIR}
[root@k8s-103 ~]# mount /dev/${DIR}/${DIR} /${DIR}/
[root@k8s-103 ~]# echo "/dev/mapper/${DIR}-${DIR} /${DIR} xfs defaults 0 0" >> /etc/fstab
[root@k8s-103 ~]# df -hT
Filesystem              Type      Size  Used Avail Use% Mounted on
devtmpfs                devtmpfs  3.9G     0  3.9G   0% /dev
tmpfs                   tmpfs     3.9G     0  3.9G   0% /dev/shm
tmpfs                   tmpfs     3.9G  9.0M  3.9G   1% /run
tmpfs                   tmpfs     3.9G     0  3.9G   0% /sys/fs/cgroup
/dev/mapper/centos-root xfs        49G  2.2G   47G   5% /
/dev/sda1               xfs      1014M  184M  831M  19% /boot
tmpfs                   tmpfs     793M     0  793M   0% /run/user/0
/dev/mapper/ssd-ssd     xfs       200G   33M  200G   1% /ssd

删除磁盘

删除磁盘则是逆序操作。下面使删除挂载卷例子:

[root@k8s-249 ~]# vi /etc/fstab
[root@k8s-249 ~]# umount /databases
[root@k8s-249 ~]# yes | lvremove /dev/databases/databases
[root@k8s-249 ~]# vgremove databases
[root@k8s-249 ~]# pvremove /dev/sdb1
[root@k8s-248 ~]# systemctl daemon-reload 
[root@k8s-249 ~]# systemctl restart proc-fs-nfsd.mount

重启

最后重启服务器,让改动生效:

[root@localhost ~]# reboot

高可用集群部署

以部署三主节点高可用集群示例,系统为 CentOS 7,K8s 版本为 1.22.3。集群构架图如下:

集群构架图

Kubeadm

Kubeadm 是由志愿者开发的专门用于部署 K8s 的工具。自 K8s 1.14 版本以后,Kubeadm 项目已经正式宣布 GA(General Availability),可以在官方文档中查看详细的使用说明。

kubeadm init

初始化的大致过程如下:

  1. kubeadm 执行初始化检查。
  2. 生成 token 和证书。
  3. 生成 KubeConfig 文件,kubelet 需要使用该文件与 Master 进行通信。
  4. 安装 Master 组件,从镜像仓库下载组件和 Docker 镜像。
  5. 安装附加组件 kube-proxy 和 kube-dns。
  6. Kubernetes Master 初始化成功。
  7. 提示如何配置 kubectl,安装 pod 网络,以及其他节点注册到集群的方法。

执行 kubeadm init 指令后,kubeadm 首先会进行一系列的检查工作,以确定本机是否适用于部署 K8s。这一步的检查被称为 Preflight Checks,其主要目的是检查内核版本、CGroups 模块、kubelet 版本、网络端口等必要的配置是否正确。

默认情况下,与 API 服务器的通信必须使用 HTTPS 方式,因此需要证书文件。在完成系统检查后,kubeadm 开始生成 K8s 对外提供服务所需的各种证书和目录。kubeadm 生成的证书存放在主节点的 /etc/kubernetes/pki/ 目录下,其中最重要的文件是 ca.crtca.key。也可以将现有的证书复制到证书目录中,这样 kubeadm 就不会生成证书:

[root@k8s-250 ~]$ ls /etc/kubernetes/pki
apiserver.crt              apiserver.key                 ca.crt  front-proxy-ca.crt      front-proxy-client.key
apiserver-etcd-client.crt  apiserver-kubelet-client.crt  ca.key  front-proxy-ca.key      sa.key
apiserver-etcd-client.key  apiserver-kubelet-client.key  etcd    front-proxy-client.crt  sa.pub

生成证书之后,kubeadm 接下来会创建用于访问 API 服务器的配置文件,并存放在 /etc/kubernetes/ 目录下。这些文件记录了主节点的服务器地址、端口和证书位置等信息,客户端可以直接使用这些信息:

[root@k8s-250 ~]$ ls /etc/kubernetes/
admin.conf  controller-manager.conf  kubeadm-master.config  kubelet.conf  manifests  pki  scheduler.conf

随后,kubeadm 会为 Master 组件生成 pod 的配置文件,并以静态 pod 的形式运行。这种容器启动的方式允许将要部署的 pod 的 YAML 文件放在一起,当 Kubelet 启动时,会自动加载这些文件并启动 pod。这些配置文件默认存放在 /etc/kubernetes/manifests/ 目录下:

[root@k8s-250 ~]$ ll /etc/kubernetes/manifests/
total 16
-rw------- 1 root root 2258 Apr 20 22:19 etcd.yaml
-rw------- 1 root root 3424 Apr 20 22:29 kube-apiserver.yaml
-rw------- 1 root root 2906 Apr 20 22:30 kube-controller-manager.yaml
-rw------- 1 root root 1492 Apr 20 22:30 kube-scheduler.yaml

然后,kubeadm 会为集群生成一个用于加入集群的 bootstrap token。其他证书等信息通过 ConfigMap 的方式保存在 etcd 中。

最后是安装默认插件,包括 kube-proxy 和 DNS 插件,用于提供整个集群的服务发现和 DNS 功能。

kubeadm join

加入集群时,需要使用主节点初始化时生成的令牌(token),该令牌用于进行一次性身份验证。工作节点在获取 ConfigMap 中的证书后,将使用证书进行安全通信。

安装组件

kubelet 是唯一没有以容器形式运行的 K8s 组件,它通过 systemd 服务运行。kubeadm 用来创建 K8s 集群,kubectl 是用来执行 K8s 命令的工具。其他 K8s 的系统组件都以容器化运行,并被放到 kube-system namespaces 中,例如 coredns、etcd、apiserver、controller-manager。

在所有要加入 K8s 集群的主机中安装 kubelet、kubeadm 和 kubectl 1.22.3 版本:

[root@k8s-101 ~]# tee /etc/yum.repos.d/kubernetes.repo<<EOF
[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
EOF
[root@k8s-101 ~]# yum install -y kubelet-1.22.3 kubeadm-1.22.3 kubectl-1.22.3

修改 Kubelet 配置文件,设置 cgroup 驱动为 systemd,镜像 pause 使用阿里云的源,然后启动 Kubelet:

[root@k8s-101 ~]# tee /etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=systemd --runtime-cgroups=/systemd/system.slice --kubelet-cgroups=/systemd/system.slice --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.2"
EOF
[root@k8s-101 ~]# systemctl daemon-reload
[root@k8s-101 ~]# systemctl enable --now kubelet

主节点配置

主节点上部署 HAProxy 和 keepalived 来保证主节点高可用。

部署 HAProxy

HAProxy 提供高可用性、负载均衡、基于 TCP 和 HTTP 的代理,支持数以万记的并发连接。此处 HAProxy 为 apiserver 提供反向代理,HAProxy 将所有请求轮询转发到每个 master 节点上。

在所有主节点上安装 HAProxy:

[root@k8s-101 ~]# yum install -y haproxy

HAProxy 的配置文件内容相同,前台监听 16443 端口,转发请求到后台 6443 端口上。主要是设置 backend kubernetes-apiserver 中 server 的地址列表,负载均衡模式 balance 默认为轮询的负载算法 roundrobin:

[root@k8s-101 ~]# tee /etc/haproxy/haproxy.cfg<<EOF
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    log         127.0.0.1 local2

    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon

    stats socket /var/lib/haproxy/stats

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

#---------------------------------------------------------------------
# kubernetes apiserver frontend which proxys to the backends
#---------------------------------------------------------------------
frontend kubernetes-apiserver
    mode                 tcp
    bind                 *:16443
    option               tcplog
    default_backend      kubernetes-apiserver

#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
backend kubernetes-apiserver
    mode        tcp
    balance     roundrobin
    server  k8s-101 192.168.1.101:6443 check
    server  k8s-102 192.168.1.102:6443 check
    server  k8s-103 192.168.1.103:6443 check

#---------------------------------------------------------------------
# collection haproxy statistics message
#---------------------------------------------------------------------
listen stats
    bind                 *:10001
    stats auth           admin:4we50meP455w0rd
    stats refresh        5s
    stats realm          HAProxy\ Statistics
    stats uri            /
EOF

启动后服务后,在浏览器访问任意一台主节点 10001 端口来查询状态。登录使用配置文件中的账号 admin 密码 4we50meP455w0rd

[root@k8s-101 ~]# systemctl enable --now haproxy.service
[root@k8s-101 ~]# systemctl status haproxy
[root@k8s-101 ~]# netstat -ntulp |grep "16443\|10001"
tcp        0      0 0.0.0.0:10001           0.0.0.0:*               LISTEN      28052/haproxy       
tcp        0      0 0.0.0.0:16443           0.0.0.0:*               LISTEN      28052/haproxy    

部署 Keepalived

Keepalived 以 VRRP(虚拟路由冗余协议)协议为基础,包括一个 master 和多个 backup。master 劫持 VIP 对外提供服务。master 发送组播,backup 节点收不到 VRRP 包时认为 master 宕机,此时选出剩余优先级最高的节点作为新 master 劫持 VIP。

此处的 Keepalived 的主要作用是为 haproxy 提供 VIP(192.168.1.253)。在 3 个 HAProxy 实例之间提供主备,降低当其中一个 HAProxy 失效时对服务的影响。

先在所有主节点上安装 Keepalived:

[root@k8s-101 ~]# yum -y install keepalived psmisc

选主节点 k8s-101 作为 Keepalived 的 MASTER ,配置文件内容如下:

[root@k8s-101 ~]# vi /etc/keepalived/keepalived.conf
global_defs {
    router_id LVS_DEVEL
    script_user root
    enable_script_security
}
vrrp_script check_haproxy {
script "/bin/bash -c 'if [[ $(netstat -nlp | grep 16443) ]]; then exit 0; else exit 1; fi'"
interval 2
weight -11
}
vrrp_instance VI_1 {
    state MASTER
    interface ens32
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 2G4phJHIK27jPXq2HJs4BG
    }
    virtual_ipaddress {
        192.168.1.253
    }
track_script {
check_haproxy
}
}

其他节点的配置修改 vrrp_instance VI_1 中角色 stateBACKUP,优先级 priority 为 90:

[root@k8s-102 ~]# vi /etc/keepalived/keepalived.conf
global_defs {
    router_id LVS_DEVEL
    script_user root
    enable_script_security
}
vrrp_script check_haproxy {
script "/bin/bash -c 'if [[ $(netstat -nlp | grep 16443) ]]; then exit 0; else exit 1; fi'"
interval 2
weight -11
}
vrrp_instance VI_1 {
    state BACKUP
    interface ens32
    virtual_router_id 51
    priority 90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 2G4phJHIK27jPXq2HJs4BG
    }
    virtual_ipaddress {
        192.168.1.253
    }
track_script {
check_haproxy
}
}

配置文件中主要配置项有:

  • vrrp_script check_haproxy{}:检测 HAProxy 进程是否存活脚本。每 2 秒检测本地 HAProxy 监听端口 16443。如果端口没响应,优先级分数会被减去 11,VIP 地址会转移到更高优先级的节点。
  • state MASTER:指定节点角色。
  • interface ens32:指定 VIP 地址绑定网卡名称。
  • virtual_router_id 51:虚拟路由组的 ID。
  • priority 100:节点优先级。
  • authentication:节点之间认证密码。
  • virtual_ipaddress{}:VIP 虚拟 IP 地址。

配置好后,在所有主节点启动服务:

[root@k8s-101 ~]# systemctl enable --now keepalived.service

注意,重启网络服务后,必须连带重启 Keepalived 服务,否则 VIP 地址会失效。

测试故障转移

使用 ip a s 查看当前 VIP 绑定的主机,并在停止绑定主机上停止 Keepalived 或 HAProxy 服务后,观察 VIP 的漂移:

[root@k8s-101 ~]# ip a s
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:f3:57:42 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.101/24 brd 192.168.1.255 scope global noprefixroute ens32
       valid_lft forever preferred_lft forever
    inet 192.168.1.253/32 scope global ens32
       valid_lft forever preferred_lft forever
    inet6 fe80::e99f:f473:5c6b:60e9/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
[root@k8s-101 ~]# systemctl stop haproxy
[root@k8s-101 ~]# netstat -ntulp |grep 16443
[root@k8s-101 ~]# ip a s
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:f3:57:42 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.101/24 brd 192.168.1.255 scope global noprefixroute ens32
       valid_lft forever preferred_lft forever
    inet6 fe80::e99f:f473:5c6b:60e9/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

在 k8s-101 上关闭 HAProxy 服务后,VIP 地址已经转移到别的节点上面。即使 k8s-101 节点重新启动也不会转移回去:

[root@k8s-103 ~]# ip a s
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:50:56:22:32:88 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.103/24 brd 192.168.1.255 scope global noprefixroute ens32
       valid_lft forever preferred_lft forever
    inet 192.168.1.253/32 scope global ens32
       valid_lft forever preferred_lft forever
    inet6 fe80::2526:9131:23cc:8de8/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

建立集群

为了组成高可用集群,至少需要 3 个主节点,否则会出现脑裂现象。

初始化主节点

采用 kubeadm 来部署集群,首先需要初始化一个主节点。

可以生成默认的 kubeadm-master.config 配置文件,并在修改后指定使用该配置运行命令:

[root@k8s-101 ~]# kubeadm config print init-defaults > /etc/kubernetes/kubeadm-master.config
[root@k8s-101 ~]# kubeadm init --config=/etc/kubernetes/kubeadm-master.config --upload-certs

或者,可以使用指定的参数来初始化主节点:

[root@k8s-101 ~]# kubeadm init --apiserver-advertise-address=192.168.1.101 --image-repository=registry.aliyuncs.com/google_containers --kubernetes-version=v1.22.3 --service-cidr=10.96.0.0/12 --pod-network-cidr=10.244.0.0/16

常见参数配置说明如下:

  • --apiserver-advertise-address:第一个主节点的 IP 地址,一般为内网地址。
  • --image-repository:设置镜像仓库地址。
  • --kubernetes-version:设置 Kubernetes 版本。版本高于 1.23 无法使用 Docker 容器运行时。
  • --service-cidr:设置服务器通信使用的 IP 网段。默认为 10.96.0.0/12
  • --pod-network-cidr:设置内部的 Pod 节点之间网络可以使用的 IP 段。对于使用不同的网络插件,默认值有所不同。常见的网络插件默认值配置如下:
    • Calico 网络插件:默认为 192.168.0.0/16
    • Flannel 网络插件:默认为 10.244.0.0/16
    • Weave 网络插件:默认为 10.32.0.0/12
  • --upload-certs:上传证书到 kubeadm-certs Secret。
  • --control-plane-endpoint:设置控制节点主机地址。

下面使用声明的变量来生成配置:

[root@k8s-101 ~]# export APISERVER_ADVERTISE_ADDRESS=192.168.1.101
[root@k8s-101 ~]# export KUBERNETES_VERSION=v1.22.3
[root@k8s-101 ~]# export SERVICE_CIDR=10.96.0.0/16
[root@k8s-101 ~]# export POD_NETWORK_CIDR=10.244.0.1/16
[root@k8s-101 ~]# export CONTROL_PLANE_ENDPOINT=192.168.1.253:16443
[root@k8s-101 ~]# mkdir -p /hxz393/local/k8s/config/
[root@k8s-101 ~]# cat <<EOF > /hxz393/local/k8s/config/kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "${KUBERNETES_VERSION}"
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
controlPlaneEndpoint: "${CONTROL_PLANE_ENDPOINT}"
networking:
  serviceSubnet: "${SERVICE_CIDR}"
  podSubnet: "${POD_NETWORK_CIDR}"
  dnsDomain: "cluster.local"
EOF
[root@k8s-101 ~]# kubeadm init --config=/hxz393/local/k8s/config/kubeadm-config.yaml --upload-certs
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/

You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join k8s-250:6443 --token 66e1on.yh84w71x6mauu6em \
        --discovery-token-ca-cert-hash sha256:5849bdad8508feeb3b40e634f7c97f074eb79705d365d097ee75a44374545715 \
        --control-plane --certificate-key 337bb228b52a88c297d72102652834aeef9666b847247b2b17471a78236af909

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.

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

kubeadm join k8s-250:6443 --token 66e1on.yh84w71x6mauu6em \
        --discovery-token-ca-cert-hash sha256:5849bdad8508feeb3b40e634f7c97f074eb79705d365d097ee75a44374545715 

上面是主节点搭建成功后,输出后续操作提示。如果因为拉取镜像时间太长导致失败,可以用下面命令提前拉取镜像:

[root@k8s-101 ~]# kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers

配置 Kubectl

按照提示,将变量声明写入到 .bashrc 中:

[root@k8s-101 ~]# echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >>~/.bashrc

或者,将 admin.conf 放入 $HOME/.kube 目录中,才能使用 kubectl 命令操作集群:

[root@k8s-101 ~]# mkdir -p $HOME/.kube && cp -i /etc/kubernetes/admin.conf $HOME/.kube/config && cp -i /etc/kubernetes/admin.conf /hxz393/local/k8s/config/admin.conf

同样地,可以将 admin.conf 放入其他用户的家目录或其他主机上,以赋予其集群管理权限。请注意修改配置文件的所有者和权限:

[root@k8s-250 ~]$ scp /etc/kubernetes/admin.conf shareuser@192.168.1.248:/home/shareuser/.kube/config
[root@k8s-248 ~]$ chown -R shareuser:shareuser /home/shareuser/.kube
[root@k8s-248 ~]$ chmod -R 755 /home/shareuser/.kube

配置 kubectl 命令的自动补全:

[root@k8s-101 ~]# echo "source <(kubectl completion bash)" >>~/.bashrc
[root@k8s-101 ~]# source ~/.bashrc

部署网络

可以选择安装高性能的 Underlay 网络,如 Flannel 或 Calico。只能安装其中一种网络。

  • Flannel

    官方网站提供的安装 Flannel 网络:

    [root@k8s-101 ~]# kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
    Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
    podsecuritypolicy.policy/psp.flannel.unprivileged created
    clusterrole.rbac.authorization.k8s.io/flannel created
    clusterrolebinding.rbac.authorization.k8s.io/flannel created
    serviceaccount/flannel created
    configmap/kube-flannel-cfg created
    daemonset.apps/kube-flannel-ds created

    由于 githubusercontent.com 经常连接不通,可以手动将 kube-flannel.yml 下载下来后部署:

    [root@k8s-101 ~]# kubectl apply -f /hxz393/local/k8s/apply/kube-flannel.yml
  • Calico

    使用以下方式安装 Calico 网络:

    [root@k8s-101 ~]# wget https://kuboard.cn/install-script/calico/calico-3.9.2.yaml
    [root@k8s-101 ~]# sed -i "s#192\.168\.0\.0/16#${pod_SUBNET}#" calico-3.9.2.yaml
    [root@k8s-101 ~]# kubectl apply -f calico-3.9.2.yaml

    根据官方网站提供的安装方式:

    [root@k8s-101 ~]# kubectl create -f https://docs.projectcalico.org/manifests/tigera-operator.yaml
    [root@k8s-101 ~]# kubectl create -f https://docs.projectcalico.org/manifests/custom-resources.yaml
    installation.operator.tigera.io/default created

完成部署

等待网络部署完成,所有 8 个容器都部署完毕后,主节点就搭建好了:

[root@k8s-101 ~]# kubectl get pod -n kube-system -o wide --watch
[root@k8s-101 ~]# journalctl -u kubelet -f

添加 Node 标签,其中 NodeRole 角色名可以是 master(主节点)或 worker(从节点)。NodeName 是主机名,NodeEnv 可以是 base(基础服务)或 app(应用):

[root@k8s-101 ~]# kubectl label node k8s-101 NodeRole=master
[root@k8s-101 ~]# kubectl label node k8s-101 NodeName=k8s-101
[root@k8s-101 ~]# kubectl label node k8s-101 NodeEnv=local

加入集群

加入集群按照以下步骤进行操作。加入失败时,可以在加入命令加上 --v=5 来显示详细错误日志。

获取参数

加入集群所需的 3 个参数如下:

  • Token

    token 的有效期为 1 天。在失效后,可以使用 kubeadm token create 命令重新创建:

    [root@k8s-101 ~]# kubeadm token create
    xjp771.yrjc4oe1thjjt112
    [root@k8s-101 ~]# kubeadm token list
    TOKEN                     TTL         EXPIRES                USAGES                   DESCRIPTION                                                EXTRA GROUPS
    xjp771.yrjc4oe1thjjt112   23h         2022-04-21T02:14:35Z   authentication,signing   <none>                                                     system:bootstrappers:kubeadm:default-node-token
  • Discovery Token CA Cert Hash

    获得 discovery-token-ca-cert-hash 参数的方法如下:

    [root@k8s-101 ~]# openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
    1499ca908be1d430887b67d4dbfff06a5374cf3d340d3c0ba9db1aa51ad9ddfb
  • Certificate Key

    直接更新证书来获取 certificate-key 参数:

    [root@k8s-101 ~]# kubeadm init phase upload-certs --upload-certs
    I0420 10:15:58.086168    6701 version.go:255] remote version is much newer: v1.23.5; falling back to: stable-1.22
    [upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
    [upload-certs] Using certificate key:
    937bf7194bd6e346e177f3a64de9ec01e9043828cb263f0ed80bddc359a351e2

加入从节点

加入从节点只需要使用 tokendiscovery-token-ca-cert-hash 参数。指定集群的 Control Plane 地址(192.168.1.253:16443)或单个主节点的 IP 地址加端口号 6443(192.168.1.101:6443),并使用 kubeadm join 命令将从节点加入集群:

[root@k8s-101 ~]# echo "192.168.1.101   k8s-101" >> /etc/hosts
[root@k8s-101 ~]# kubeadm join k8s-101:6443 --token xjp771.yrjc4oe1thjjt112 --discovery-token-ca-cert-hash sha256:1499ca908be1d430887b67d4dbfff06a5374cf3d340d3c0ba9db1aa51ad9ddfb
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

加入成功后,在主节点给新节点标签:

[root@k8s-101 ~]# kubectl label node k8s-104 NodeRole=worker
[root@k8s-101 ~]# kubectl label node k8s-104 NodeName=k8s-104
[root@k8s-101 ~]# kubectl label node k8s-104 NodeEnv=local
[root@k8s-101 ~]# kubectl label node k8s-104 node-role.kubernetes.io/worker=app

加入主节点

加入主节点的步骤与加入从节点类似,多需要提供一个 --certificate-key 参数:

[root@k8s-101 ~]# echo "192.168.1.101   k8s-101" >> /etc/hosts
[root@k8s-101 ~]# kubeadm join k8s-101:644 --token 4sz9hf.dwt05n1ohpb772au \
        --discovery-token-ca-cert-hash sha256:864ee6f29ac83d7ec0db3e75c2b54a70ac7622c8c82e52cc71511255febf5b28 \
        --control-plane --certificate-key af928d1032d723ae3f16c7218b07afb41b4d588d746bbeb6ed9c657a460591cd

添加集群控制权限:

[root@k8s-101 ~]# mkdir -p $HOME/.kube && cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@k8s-101 ~]# echo "source <(kubectl completion bash)" >>~/.bashrc
[root@k8s-101 ~]# source ~/.bashrc

添加节点标签:

[root@k8s-101 ~]# kubectl label node k8s-249 NodeRole=master
[root@k8s-101 ~]# kubectl label node k8s-249 NodeName=k8s-249
[root@k8s-101 ~]# kubectl label node k8s-249 NodeEnv=base

等待完成

查看已加入的节点:

[root@k8s-101 ~]# kubectl get node
NAME      STATUS     ROLES                  AGE   VERSION
k8s-101   Ready      control-plane,master   25m   v1.22.3
k8s-104   NotReady   <none>                 95s   v1.22.3
k8s-105   NotReady   <none>                 95s   v1.22.3
k8s-106   NotReady   <none>                 87s   v1.22.3

使用 --all-namespaces 参数来查看所有节点上的容器运行信息,确保所有的 Pod 都处于运行状态:

[root@k8s-101 ~]# kubectl get pod --all-namespaces -o wide
NAMESPACE     NAME                              READY   STATUS    AGE     IP              NODE      
kube-system   coredns-7d89d9b6b8-6p4wt          1/1     Running   26m     10.244.0.2      k8s-101   
kube-system   coredns-7d89d9b6b8-dd5h6          1/1     Running   26m     10.244.0.3      k8s-101   
kube-system   etcd-k8s-101                      1/1     Running   26m     192.168.1.101   k8s-101   
kube-system   kube-apiserver-k8s-101            1/1     Running   26m     192.168.1.101   k8s-101   
kube-system   kube-controller-manager-k8s-101   1/1     Running   26m     192.168.1.101   k8s-101   
kube-system   kube-flannel-ds-bhck2             1/1     Running   2m17s   192.168.1.105   k8s-105   
[root@k8s-101 ~]# kubectl get all --all-namespaces 

移除节点

如果想从集群中移除节点,首先使用 drain 命令驱逐节点上运行的 Pod:

[root@k8s-250 ~]$ kubectl drain k8s-249 --ignore-daemonsets
node/k8s-249 cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/kube-flannel-ds-8rfqm, kube-system/kube-proxy-qnxcw
evicting pod base/zookeeper-2
evicting pod base/zookeeper-0

在所有的 Pod 转移完成后,可以运行以下命令删除节点(主节点还需要手动修改 etcd 数据库):

[root@k8s-250 ~]$ kubectl delete node k8s-249
node "k8s-249" deleted

最后在要移除的节点上运行以下命令:

[root@k8s-249 ~]$ yes | kubeadm reset
[root@k8s-249 ~]$ rm -rf /var/lib/cni/ /etc/cni/net.d $HOME/.kube/config /var/lib/etcd
[root@k8s-249 ~]$ ifconfig cni0 down
[root@k8s-249 ~]$ ip link delete cni0
[root@k8s-249 ~]$ ifconfig flannel.1 down
[root@k8s-249 ~]$ ip link delete flannel.1

节点配置

安装完毕后还需要做一些配置修改和初始化工作。

主节点配置

开发环境下,为了允许在主节点上部署应用程序,需要移除主节点上的污点(去除 taint 命令最后的减号 - 则是重新加入污点):

[root@k8s-101 ~]# kubectl taint nodes --all node-role.kubernetes.io/master=true:NoSchedule-
node/k8s-101 untainted
node/k8s-102 untainted
node/k8s-103 untainted
[root@k8s-101 ~]# kubectl get no -o yaml | grep taint -A 5

修改 NodePort 端口范围为无限制。在 /etc/kubernetes/manifests/kube-apiserver.yaml 文件中加入一行配置 - --service-node-port-range=1-65535 ,配置会自动更新:

[root@k8s-101 ~]# sed -i '40a\    - --service-node-port-range=1-65535' /etc/kubernetes/manifests/kube-apiserver.yaml
[root@k8s-101 ~]# cat /etc/kubernetes/manifests/kube-apiserver.yaml
    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
    - --service-node-port-range=1-65535
    image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.22.3
    imagePullPolicy: IfNotPresent

注释掉 /etc/kubernetes/manifests/kube-controller-manager.yaml/etc/kubernetes/manifests/kube-scheduler.yaml 文件中的端口绑定,免得健康检查一直报错:

[root@k8s-101 ~]# sed -i "s/    - --port=0/#    - --port=0/g" /etc/kubernetes/manifests/kube-controller-manager.yaml
[root@k8s-101 ~]# sed -i "s/    - --port=0/#    - --port=0/g" /etc/kubernetes/manifests/kube-scheduler.yaml
[root@k8s-101 ~]# sed -i "s/    - --bind-address=127.0.0.1/    - --bind-address=0.0.0.0/g" /etc/kubernetes/manifests/kube-controller-manager.yaml
[root@k8s-101 ~]# sed -i "s/    - --bind-address=127.0.0.1/    - --bind-address=0.0.0.0/g" /etc/kubernetes/manifests/kube-scheduler.yaml
[root@k8s-101 ~]# systemctl restart kubelet.service
[root@k8s-101 ~]# kubectl get cs
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME                 STATUS    MESSAGE                         ERROR
scheduler            Healthy   ok                              
controller-manager   Healthy   ok                              
etcd-0               Healthy   {"health":"true","reason":""}   

修改节点预留服务器资源,预留 1 核 CPU 加 1 GB 内存:

[root@k8s-101 ~]# sed -i '38a\systemReserved:\n  cpu: "500m"\n  memory: "500Mi"\nkubeReserved:\n  cpu: "500m"\n  memory: "500Mi"' /var/lib/kubelet/config.yaml
[root@k8s-101 ~]# systemctl restart kubelet.service

从节点配置

修改节点预留服务器资源:

[root@k8s-104 ~]# sed -i '38a\systemReserved:\n  cpu: "500m"\n  memory: "500Mi"\nkubeReserved:\n  cpu: "500m"\n  memory: "500Mi"' /var/lib/kubelet/config.yaml
[root@k8s-104 ~]# systemctl restart kubelet.service

集群配置

以下是一些常见的配置项目。

建立命名空间

可依据需要建立多命名空间,来区分不同的环境。例如:用 dev 表示开发环境、用 test 表示测试环境、用 base 表示基础服务环境:

[root@k8s-101 ~]# tee /hxz393/local/k8s/apply/kube-namespaces.yml<<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: local
EOF
[root@k8s-101 ~]# kubectl apply -f /hxz393/local/k8s/apply/kube-namespaces.yml
[root@k8s-101 ~]# kubectl get namespaces 
NAME              STATUS   AGE
default           Active   69m
kube-node-lease   Active   69m
kube-public       Active   69m
kube-system       Active   69m
local             Active   23m

绑定权限

有时为了测试目的,需要将集群管理员角色绑定到对应命名空间默认 sa(service account)账号上。这样默认命名空间中应用可以访问其他命名空间的资源:

[root@k8s-101 ~]# tee /nanruan/test/k8s/apply/kube-crb.yml<<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-crb
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: default
  namespace: local
EOF
[root@k8s-101 ~]# kubectl apply -f /nanruan/test/k8s/apply/test-crb.yml

阿里云部署

在阿里云上部署 Kubernetes 时,ECS 不支持自建 VIP。但将 SLB 的 VIP 指定为高可用地址时,初始化主节点会失败。这是因为 4 层 SLB 不支持其调度的后端服务器访问其 VIP,也就是服务器不能同时充当服务端和客户端。

解决方法如下:

  • 在从节点部署 HAProxy,并监听 16443 端口,后端服务器指向主节点 6443 端口。
  • 在 CLB(传统型负载均衡,原 SLB)上新建虚拟服务器组 k8s-haproxy,将安装有 HAProxy 的从节点加进去,监听端口为 16443
  • 在监听页新建监听配置 k8s_haproxy,选择监听 TCP/6443 端口,后端服务器选择虚拟服务器组 k8s-haproxy,开启健康检查,提交。
  • 如果不需要通过外网来管理集群,可以修改监听 k8s_haproxy 的访问控制。例如添加内网 IP 地址段 172.16.0.0/16 到白名单,这样可以防止集群对外暴露。
  • 在建立集群时,配置 controlPlaneEndpoint 的值为 <CLB内网IP地址>:6443。例如:--control-plane-endpoint=172.16.17.100:6443
  • 加入集群时,使用controlPlaneEndpoint 的值。例如:kubeadm join 172.16.17.100:6443

如果需要通过外网来管理集群,可在主节点上将 CLB 的外网 IP 地址更新进去,重新生成证书:

[root@k8s-master1-pro ~]# mkdir -pv /opt/cert && mv /etc/kubernetes/pki/apiserver.* /opt/cert
[root@k8s-master1-pro ~]# kubeadm init phase certs apiserver --apiserver-advertise-address 172.16.17.100 --apiserver-cert-extra-sans 110.112.110.102 --apiserver-cert-extra-sans 172.16.17.99 --apiserver-cert-extra-sans 80.114.163.80

其他关于解决此问题的参考连接:

https://www.cnblogs.com/gmmy/p/12372805.html

https://www.cnblogs.com/noah-luo/p/13218837.html

https://zhuanlan.zhihu.com/p/107703112

https://blog.51cto.com/caiyuanji/2405434

https://www.jianshu.com/p/ba94db1c3599

https://blog.csdn.net/weixin_44334279/article/details/119355653

https://www.jianshu.com/p/d645bbe8e621

https://www.kubernetes.org.cn/7033.html

单点集群部署

这里以一主一从的方式,搭建单主节点集群。主节点系统为 Ubuntu。

安装组件

在所有节点安装基础组件 Kubectl、Kubelet 和 Kubeadm,版本选择为 1.23.17:

root@ubuntu22:~# curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add - 
root@ubuntu22:~# apt-add-repository -y "deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main"
root@ubuntu22:~# apt install -y kubelet=1.23.17-00 kubeadm=1.23.17-00 kubectl=1.23.17-00
root@ubuntu22:~# tee /etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=systemd --runtime-cgroups=/systemd/system.slice --kubelet-cgroups=/systemd/system.slice --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.2"
EOF
root@ubuntu22:~# systemctl daemon-reload
root@ubuntu22:~# systemctl enable --now kubelet

建立集群

首先初始化一个主节点。

声明变量

设置的控制中心地址本机 IP 地址:

root@ubuntu22:~# export APISERVER_ADVERTISE_ADDRESS=192.168.0.102
root@ubuntu22:~# export KUBERNETES_VERSION=v1.23.17
root@ubuntu22:~# export SERVICE_CIDR=10.96.0.0/12
root@ubuntu22:~# export POD_NETWORK_CIDR=10.244.0.0/16
root@ubuntu22:~# export CONTROL_PLANE_ENDPOINT=192.168.0.102:6443

初始化

使用 kubeadm init 命令执行初始化:

root@ubuntu22:~# mkdir -p /hxz393/local/k8s/{apply,config}
root@ubuntu22:~# cat <<EOF > /hxz393/local/k8s/config/kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "${KUBERNETES_VERSION}"
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
controlPlaneEndpoint: "${CONTROL_PLANE_ENDPOINT}"
networking:
  serviceSubnet: "${SERVICE_CIDR}"
  podSubnet: "${POD_NETWORK_CIDR}"
  dnsDomain: "cluster.local"
EOF
root@ubuntu22:~# kubeadm init --config=/hxz393/local/k8s/config/kubeadm-config.yaml --upload-certs

成功后输出后续操作提示,可以记一下加入集群命令。

获取权限

获取 root 用户集群管理权:

root@ubuntu22:~# mkdir -p $HOME/.kube && cp -i /etc/kubernetes/admin.conf $HOME/.kube/config && cp -i /etc/kubernetes/admin.conf /hxz393/local/k8s/config/admin.conf
root@ubuntu22:~# echo "source <(kubectl completion bash)" >>~/.bashrc
root@ubuntu22:~# source ~/.bashrc

部署网络

部署 flannel 网络插件:

root@ubuntu22:~# kubectl apply -f /hxz393/local/k8s/apply/kube-flannel.yml

完成部署

给主节点添加标签:

root@ubuntu22:~# kubectl label node ubuntu22 NodeRole=master
root@ubuntu22:~# kubectl label node ubuntu22 NodeName=ubuntu22
root@ubuntu22:~# kubectl label node ubuntu22 NodeEnv=pre

加入集群

加入从节点只需要运行集群建立后的提示命令:

[root@centos7 ~]# echo "192.168.0.102   ubuntu22" >> /etc/hosts
[root@centos7 ~]# kubeadm join 192.168.0.102:6443 --token 8owkqz.jopga29w7j9f8q6k \
        --discovery-token-ca-cert-hash sha256:1f796318f606fafa7aa1e8429707c752533b5d534da0108f30b5a423bd386215 

添加从节点标签:

root@ubuntu22:~# kubectl label node centos7 NodeName=centos7
root@ubuntu22:~# kubectl label node centos7 NodeEnv=pre
root@ubuntu22:~# kubectl label node centos7 node-role.kubernetes.io/worker=app

主节点配置

视需要移除主节点上的污点来允许部署:

root@ubuntu22:~# kubectl taint nodes --all node-role.kubernetes.io/master=true:NoSchedule-

修改静态配置:

root@ubuntu22:~# sed -i '40a\    - --service-node-port-range=1-65535' /etc/kubernetes/manifests/kube-apiserver.yaml
root@ubuntu22:~# sed -i '40a\    - --service-node-port-range=1-65535' /etc/kubernetes/manifests/kube-apiserver.yaml
root@ubuntu22:~# sed -i "s/    - --port=0/#    - --port=0/g" /etc/kubernetes/manifests/kube-controller-manager.yaml
root@ubuntu22:~# sed -i "s/    - --port=0/#    - --port=0/g" /etc/kubernetes/manifests/kube-scheduler.yaml
root@ubuntu22:~# sed -i "s/    - --bind-address=127.0.0.1/    - --bind-address=0.0.0.0/g" /etc/kubernetes/manifests/kube-controller-manager.yaml
root@ubuntu22:~# sed -i "s/    - --bind-address=127.0.0.1/    - --bind-address=0.0.0.0/g" /etc/kubernetes/manifests/kube-scheduler.yaml
root@ubuntu22:~# sed -i '38a\systemReserved:\n  cpu: "500m"\n  memory: "500Mi"\nkubeReserved:\n  cpu: "500m"\n  memory: "500Mi"' /var/lib/kubelet/config.yaml
root@ubuntu22:~# systemctl restart kubelet.service

建立命名空间

建立命名空间 local 表示本地发布环境:

root@ubuntu22:~# tee /hxz393/local/k8s/apply/kube-namespaces.yml<<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: pre
EOF
root@ubuntu22:~# kubectl apply -f /hxz393/local/k8s/apply/kube-namespaces.yml

集群升级

整个集群升级需要分三步进行:

  • 第一步是主节点升级 kubeadmkubectl
  • 第二步是主节点升级 kubelet
  • 第三步是从节点升级 kubelet

主节点升级 kubeadmkubectl 不会对集群的运行产生影响:

[root@k8s-250 ~]$ yum list | grep 'kubeadm\|kubectl\|kubelet'
kubeadm.x86_64                           1.22.3-0                      @kubernetes
kubectl.x86_64                           1.22.3-0                      @kubernetes
kubelet.x86_64                           1.22.3-0                      @kubernetes
kubeadm.x86_64                           1.23.5-0                      kubernetes
kubectl.x86_64                           1.23.5-0                      kubernetes
kubelet.x86_64                           1.23.5-0                      kubernetes
[root@k8s-250 ~]$ yum -y install kubeadm-1.23.5-0 kubectl-1.23.5-0
[root@k8s-250 ~]$ kubeadm upgrade plan
[root@k8s-250 ~]$ kubeadm upgrade apply v1.23.5-0 --config /nanruan/base/k8s/kubeadm/kubeadm-config.yaml

主节点和从节点升级 kubelet 的步骤是一样的。首先禁用调度,然后升级并重启 kubelet,最后恢复允许调度:

[root@k8s-250 ~]$ kubectl drain k8s-250 --ignore-daemonsets
[root@k8s-250 ~]$ yum -y install kubelet-1.23.5-0
[root@k8s-250 ~]$ systemctl daemon-reload
[root@k8s-250 ~]$ systemctl restart kubelet
[root@k8s-250 ~]$ systemctl status kubelet
[root@k8s-250 ~]$ kubectl uncordon k8s-250
[root@k8s-250 ~]$ kubectl get nodes
[root@k8s-250 ~]$ kubectl get all --all-namespaces

故障处理

一些集群组建时遇到的问题。

没有 pod-network-cidr

在安装 Flannel 时,出现错误提示缺少分配的 Pod CIDR。这可能是因为在使用 kubeadm 初始化时没有指定 --pod-network-cidr 参数,或者 kube-flannel.yml 文件中的 “Network” 字段与 --pod-network-cidr 参数不一致:

[root@server4-master ~]$ kubectl logs -n kube-system kube-flannel-ds-7xp24 
I1103 04:42:22.796211       1 vxlan.go:137] VXLAN config: VNI=1 Port=0 GBP=false Learning=false DirectRouting=false
E1103 04:42:22.797028       1 main.go:325] Error registering network: failed to acquire lease: node "server4-master" pod cidr not assigned
I1103 04:42:22.797248       1 main.go:439] Stopping shutdownHandler...

要解决此问题,可以修改 kube-controller-manager.yaml 配置文件,在其中添加 - --allocate-node-cidrs=true- --cluster-cidr=10.244.0.0/16 参数:

[root@server4-master ~]$ vi /etc/kubernetes/manifests/kube-controller-manager.yaml
spec:
  containers:
  - command:
    - kube-controller-manager
    - --allocate-node-cidrs=true
    - --cluster-cidr=10.244.0.0/16

然后,删除错误的 Flannel 容器并重新创建,这样就可以使用正确的配置了:

[root@server4-master ~]$ kubectl delete pod -n kube-system kube-flannel-*
[root@server4-master ~]$ kubectl cluster-info dump | grep -m 1 cluster-cidr
                            "--cluster-cidr=10.244.0.0/16",

没有 controlPlaneEndpoint

在将第二个主节点加入已存在的单节点集群过程中,预检阶段会报错缺少 controlPlaneEndpoint。这是因为在初始化时没有指定 --control-plane-endpoint 参数导致的:

[root@server1 ~]$ kubeadm join 192.168.2.204:6443 --token nre8hl.4awo2mnyxr5l0tsu      --discovery-token-ca-cert-hash sha256:8055ad40e280bc4b48b0c3b4daea9aa3f515d3b2fea5fb3ae3fcad398c325a52      --control-plane --certificate-key 5ac22cea4cfc19753cd289c96b968957554b4e4204fe47fc3f56a53dded96716
error execution phase preflight: 
One or more conditions for hosting a new control plane instance is not satisfied.

unable to add a new control plane instance a cluster that doesn't have a stable controlPlaneEndpoint address

Please ensure that:
* The cluster has a stable controlPlaneEndpoint address.
* The certificates that must be shared among control plane instances are provided.

要解决此问题,可以使用 kubectl edit 命令编辑 kubeadm-config 配置文件,并将 controlPlaneEndpoint 地址添加为虚拟 IP 或正在运行的主节点的 IP:

[root@server4-master ~]$ kubectl edit cm kubeadm-config -n kube-system
    imageRepository: registry.aliyuncs.com/google_containers
    kind: ClusterConfiguration
    controlPlaneEndpoint: 192.168.2.204:6443
    kubernetesVersion: v1.22.3
    networking:
      dnsDomain: cluster.local
      podSubnet: 10.244.0.0/16

保存并退出编辑器。然后,重新执行 kubeadm join 命令将第二个主节点加入集群。这样就能成功添加第二个主节点,完成集群的扩容。

Token 过期

当加入集群时出现错误:

x509: certificate has expired or is not yet valid

可以重新生成令牌:

[root@k8s-master ~]$ kubeadm token create
87668a.lfvdqsr3l5ijlode
[root@k8s-master ~]$ kubeadm token list
TOKEN                     TTL       EXPIRES                     USAGES                   DESCRIPTION   EXTRA GROUPS
1yrznt.lzj1kl0qecosl4es   23h       2019-07-21T13:14:33+08:00   authentication,signing   <none>        system:bootstrappers:kubeadm:default-node-token
87668a.lfvdqsr3l5ijlode   23h       2019-07-21T13:24:28+08:00   authentication,signing   <none>        system:bootstrappers:kubeadm:default-node-token
[root@server7-master ~]$ kubeadm init phase upload-certs --upload-certs

然后在客户端同步时间,并再次尝试加入集群:

[root@k8s-node1 ~]$ ntpdate cn.pool.ntp.org
20 Jul 13:25:59 ntpdate[118642]: step time server 199.182.204.197 offset 137559.272548 sec

证书过期

可以使用以下命令查看证书的有效期:

[root@server4-master ~]$ kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Nov 03, 2022 05:36 UTC   217d                                    no      
apiserver                  Nov 03, 2022 05:36 UTC   217d            ca                      no      
apiserver-etcd-client      Nov 03, 2022 05:36 UTC   217d            etcd-ca                 no      
apiserver-kubelet-client   Nov 03, 2022 05:36 UTC   217d            ca                      no      
controller-manager.conf    Nov 03, 2022 05:36 UTC   217d                                    no      
etcd-healthcheck-client    Nov 03, 2022 05:36 UTC   217d            etcd-ca                 no      
etcd-peer                  Nov 03, 2022 05:36 UTC   217d            etcd-ca                 no      
etcd-server                Nov 03, 2022 05:36 UTC   217d            etcd-ca                 no      
front-proxy-client         Nov 03, 2022 05:36 UTC   217d            front-proxy-ca          no      
scheduler.conf             Nov 03, 2022 05:36 UTC   217d                                    no      

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Nov 01, 2031 05:36 UTC   9y              no      
etcd-ca                 Nov 01, 2031 05:36 UTC   9y              no      
front-proxy-ca          Nov 01, 2031 05:36 UTC   9y              no   

分别在所有主节点上重新生成所有证书:

[root@server4-master ~]$ kubeadm certs renew all
[renew] Reading configuration from the cluster...
[renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'

certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed
certificate for the front proxy client renewed
certificate embedded in the kubeconfig file for the scheduler manager to use renewed

Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager, kube-scheduler and etcd, so that they can use the new certificates.

根据提示分别在所有主节点上重启组件容器:

[root@server4-master ~]$ docker ps | grep -E 'k8s_kube-apiserver|k8s_kube-controller-manager|k8s_kube-scheduler|k8s_etcd_etcd' | awk -F ' ' '{print $1}' |xargs docker restart

更新管理配置文件:

[root@server4-master ~]$ mkdir -p $HOME/.kube && cp -i /etc/kubernetes/admin.conf $HOME/.kube/config && cp -i /etc/kubernetes/admin.conf /hxz393/local/k8s/config/admin.conf

证书不符

如果在加入集群时出现错误:

x509: certificate is valid for 10.96.0.1, 192.168.2.204, not 192.168.2.199

提示证书的 IP 地址不符合要求,这通常在更新节点的 apiserver-advertise-addresscontrolPlaneEndpoint 时会发生。解决方法是删除当前集群下 apiserver 的证书和密钥,然后重新生成:

[root@server4 ~]$ rm -f /etc/kubernetes/pki/apiserver.*

设置原先主机的 IP 地址为 192.168.2.204,现在的虚拟 IP 地址为 192.168.2.199:

[root@server4 ~]$ kubeadm init phase certs apiserver --apiserver-advertise-address 192.168.2.204 --apiserver-cert-extra-sans 192.168.2.199
[root@server4 ~]$ kubeadm alpha certs renew admin.conf
Kubeadm experimental sub-commands

重启 API Server,然后查看证书:

[root@server4 ~]$ kubectl -n kube-system delete pod kube-apiserver-server4 kube-apiserver-server6
pod "kube-apiserver-server4" deleted
pod "kube-apiserver-server6" deleted
[root@server4 ~]$ openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 2687592491658220129 (0x254c3f21b64b9261)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=kubernetes
        Validity
            Not Before: Apr  4 16:21:14 2022 GMT
            Not After : Apr 19 13:48:19 2023 GMT

将新的 admin.conf 文件复制到其他需要访问集群的主节点上:

[root@server4 ~]$ scp /etc/kubernetes/admin.conf root@192.168.2.206:./.kube

现在可以加入新的主节点了。

cni0 地址错误

在重新构建 Kubernetes 集群后,由于节点上残留了未删除的虚拟网卡,导致创建 Pod 失败,提示 cni0 地址冲突:

[root@server7-master ~]$ kubectl describe po kubia-74967b5695-ftk77
  Warning  FailedCreatePodSandBox  56s (x4 over 59s)   kubelet            (combined from similar events): Failed to create pod sandbox: rpc error: code = Unknown desc = failed to set up sandbox container "18dd9bc5076666ed605b2d3331864859454878d7e1f9fae2160f1be71c59c48f" network for pod "kubia-74967b5695-ftk77": networkPlugin cni failed to set up pod "kubia-74967b5695-ftk77_default" network: failed to delegate add: failed to set bridge addr: "cni0" already has an IP address different from 10.100.4.1/24
[root@server6 ~]$ ip a
5: cni0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether 92:1d:b1:d9:ba:19 brd ff:ff:ff:ff:ff:ff
    inet 10.100.9.1/24 brd 10.100.9.255 scope global cni0
       valid_lft forever preferred_lft forever

解决方法是手动删除网卡,让它重新创建:

[root@server6 ~]$ ifconfig cni0 down
[root@server6 ~]$ ip link delete cni0
[root@server6 ~]$ ip a
926: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
    link/ether 92:6a:c8:8b:2c:5b brd ff:ff:ff:ff:ff:ff
    inet 10.100.4.1/24 brd 10.100.4.255 scope global cni0
       valid_lft forever preferred_lft forever

etcd 错误

在 etcd 中存在已被删除的节点记录时,当节点重新加入集群时会报错:

etcd cluster is not healthy: failed to dial endpoint

这时需要手动维护 etcd 数据库。

进入节点的 etcd 容器内部,查询 etcd 集群的成员列表,然后删除错误的成员,这样集群就恢复正常了:

[root@server6 ~]$ kubectl -n kube-system exec -it etcd-k8s-239 -- sh
sh-5.0# export ETCDCTL_API=3
sh-5.0# alias etcdctl='etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key'
sh-5.0# etcdctl member list
2e1d3dc8363ee756, started, server5, https://192.168.2.205:2380, https://192.168.2.205:2379, false
4b8b623fba4c1f57, started, server6, https://192.168.2.206:2380, https://192.168.2.206:2379, false
ad703513a2c4e54a, started, server4, https://192.168.2.204:2380, https://192.168.2.204:2379, false
sh-5.0# etcdctl member remove 63bfe05c4646fb08

没找到 resolv.conf 文件

如果主节点为 Ubuntu,从节点为 CentOS,从节点加入集群后,kube-flannel 容器报错:

Failed to create pod sandbox: open /run/systemd/resolve/resolv.conf: no such file or directory

这是由于 CentOS 和 Ubuntu 系统 DNS 服务器配置文件 resolv.conf 所在位置不同导致。

可以在 CentOS 上创立一个软链接,将 /etc/resolv.conf 文件链接到 /run/systemd/resolve/resolv.conf

[root@centos7 ~]# mkdir /run/systemd/resolve/
[root@centos7 ~]# ln -s /etc/resolv.conf /run/systemd/resolve/resolv.conf

重启 kubelet 服务以使更改生效:

[root@centos7 ~]# systemctl restart kubelet

至此报错解决。

备份恢复

单主节点集群一定要做好备份工作。

查看 etcd 配置

在使用 kubeadm 部署的 Kubernetes 集群中,etcd 作为容器运行,并通过本地挂载来存储数据库和配置文件:

[root@server7-master ~]$ ll /var/lib/etcd/member/snap
total 5124
-rw-r--r--  1 root root    9851 Apr  1 03:37 0000000000000012-00000000000222f1.snap
-rw-r--r--  1 root root    9851 Apr  1 04:46 0000000000000012-0000000000024a02.snap
-rw-r--r--  1 root root    9851 Apr  1 05:55 0000000000000012-0000000000027113.snap
-rw-r--r--  1 root root    9851 Apr  1 07:04 0000000000000012-0000000000029824.snap
-rw-r--r--  1 root root    9851 Apr  1 08:14 0000000000000012-000000000002bf35.snap
-rw-------. 1 root root 5185536 Apr  1 08:58 db
[root@server7-master ~]$ ll /etc/kubernetes/pki/etcd
total 32
-rw-r--r--. 1 root root 1086 Mar 31 11:49 ca.crt
-rw-------. 1 root root 1679 Mar 31 11:49 ca.key
-rw-r--r--. 1 root root 1159 Mar 31 11:49 healthcheck-client.crt
-rw-------. 1 root root 1675 Mar 31 11:49 healthcheck-client.key
-rw-r--r--. 1 root root 1212 Mar 31 11:49 peer.crt
-rw-------. 1 root root 1679 Mar 31 11:49 peer.key
-rw-r--r--. 1 root root 1212 Mar 31 11:49 server.crt
-rw-------. 1 root root 1675 Mar 31 11:49 server.key

其中,以 “.snap” 结尾的文件是快照文件。

安装 etcd 客户端

在主节点上安装 etcd 客户端:

[root@server7-master ~]$ yum install -y etcd
[root@server7-master ~]$ etcdctl --version
etcdctl version: 3.3.11
API version: 2

查看 etcd 数据库

首先尝试连接到本地 etcd 数据库:

[root@server7-master ~]$ export ETCDCTL_API=3
[root@server4-master ~]$ etcdctl version
etcdctl version: 3.3.11
API version: 3.3
[root@server7-master ~]$ export ETCD="etcdctl --endpoints https://127.0.0.1:2379 --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key --cacert=/etc/kubernetes/pki/etcd/ca.crt"
[root@server7-master ~]$ $ETCD member list
273040abda86d599, started, server9-node1, https://192.168.2.209:2380, https://192.168.2.209:2379
4c80ad008d949110, started, server8-node1, https://192.168.2.208:2380, https://192.168.2.208:2379
dcddbc08262b69e1, started, server7-master, https://192.168.2.207:2380, https://192.168.2.207:2379

查看所有 Pod 对象的存储名称:

[root@server7-master ~]$ $ETCD get /registry/pods --prefix --keys-only
/registry/pods/default/kubia-74967b5695-bzxtf
/registry/pods/default/kubia-74967b5695-vhjkz

使用命令备份恢复

使用 etcdctl snapshot save 命令将 etcd 数据库备份到当前目录:

[root@server7-master ~]$ $ETCD snapshot save snap.db
Snapshot saved at snap.db
[root@server7-master ~]$ $ETCD snapshot status snap.db
c477d6ef, 144130, 1216, 5.2 MB

首先对一个正在运行的服务进行缩容:

[root@server7-master ~]$ kubectl get po
NAME                     READY   STATUS    RESTARTS        AGE
kubia-74967b5695-bzxtf   1/1     Running   2 (7h19m ago)   20h
kubia-74967b5695-vhjkz   1/1     Running   2 (7h19m ago)   20h
kubia-74967b5695-wsd5c   1/1     Running   2 (7h19m ago)   20h
[root@server7-master ~]$ kubectl scale deployment kubia --replicas=1
deployment.apps/kubia scaled

然后暂停 API Server 和 etcd 容器:

[root@server7-master ~]$ systemctl stop kubelet
[root@server7-master ~]$ docker stop $(docker ps -q) & docker rm -f $(docker ps -aq)
[root@server7-master ~]$ mv /etc/kubernetes/manifests /etc/kubernetes/manifests.bak
[root@server7-master ~]$ mv /var/lib/etcd/ /var/lib/etcd.bak

恢复 etcd 数据库:

[root@server7-master ~]$ $ETCD snapshot restore snap.db --data-dir=/var/lib/etcd
2022-04-01 09:40:54.304594 I | mvcc: restore compact to 143285
2022-04-01 09:40:54.315885 I | etcdserver/membership: added member 8e9e05c52164694d [http://localhost:2380] to cluster cdf818194e3a8c32

恢复运行 API Server 和 etcd 容器后,再次验证数据:

[root@server7-master ~]$ mv /etc/kubernetes/manifests.bak /etc/kubernetes/manifests
[root@server7-master ~]$ systemctl start kubelet

使用文件备份恢复

由于版本问题,使用 etcdctl 备份恢复的数据库经常出错,因此可以使用备份文件夹的方式来备份数据库:

[root@server4 ~]$ cp -r /var/lib/etcd/ /root/etcd/
[root@server4 ~]$ ll /root/etcd/
total 0
drwx------. 4 root root 29 Apr  5 02:08 member

进行一系列操作后:

[root@server4 ~]$ kubectl scale deployment kubia --replicas=11
deployment.apps/kubia scaled
[root@server4 ~]$ kubectl create ns pro
namespace/pro created

停止 kubelet,删除所有 Docker 容器,删除原数据库文件,然后将备份文件夹复制回来,启动 kubelet:

[root@server4 ~]$ systemctl stop kubelet
[root@server4 ~]$ docker stop $(docker ps -q) & docker rm -f $(docker ps -aq)
[root@server4 ~]$ rm -rf /var/lib/etcd/ 
[root@server4 ~]$ cp -r /root/etcd/ /var/lib/etcd/
[root@server4 ~]$ systemctl start kubelet

恢复的数据将回到备份时的状态。