0%

Centos安装k8s集群

本文主要包括:

  • Centos安装k8s集群

Centos安装k8s集群

安装环境说明

内存:2GB或更多RAM
CPU: 2核CPU或更多CPU
硬盘: 30GB或更多
本次环境说明:
操作系统:CentOS 7.9
内核版本:3.10.0-1160.el7.x86_64
ddp1: 172.16.7.135
ddp2: 172.16.7.136
ddp3: 172.16.7.137

环境准备

  1. 关闭防火墙和selinux
    ##关闭防火墙
    systemctl stop firewalld && systemctl disable firewalld && iptables -F
    ## 关闭selinux
    sed -i 's/enforcing/disabled/' /etc/selinux/config && setenforce 0
    ## 关闭swap分区
    swapoff -a
    ## 永久关闭swap
    sed -ri 's/.*swap.*/#&/' /etc/fstab
  2. 修改hosts
  3. 修改内核参数
    cat > /etc/sysctl.d/k8s.conf << EOF
    net.bridge.bridge-nf-call-ip6tables = 1
    net.bridge.bridge-nf-call-iptables = 1
    net.ipv4.ip_forward = 1
    EOF
    
    sysctl --system
  4. 加载ip_vs内核模块
    如果kube-proxy 模式为ip_vs则必须加载,本文采用iptables
    modprobe ip_vs
    modprobe ip_vs_rr
    modprobe ip_vs_wrr
    modprobe ip_vs_sh
    modprobe nf_conntrack_ipv4
    设置下次开机自动加载
    cat > /etc/modules-load.d/ip_vs.conf << EOF 
    ip_vs
    ip_vs_rr
    ip_vs_wrr
    ip_vs_sh
    nf_conntrack_ipv4
    EOF

    安装docker

  5. 配置yum源并安装docker
    yum install wget -y 
    wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
    yum install docker-ce docker-ce-cli -y
  6. 编辑docker配置文件
    mkdir /etc/docker/ 
    cat > /etc/docker/daemon.json << EOF
    {
    "registry-mirrors": ["https://gqs7xcfd.mirror.aliyuncs.com","https://hub-mirror.c.163.com"],
    "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
    "log-opts": {
    "max-size": "100m"
    },
    "storage-driver": "overlay2"
    }
    EOF
  7. 启动docker服务
    systemctl daemon-reload && systemctl enable docker && systemctl start docker

    安装kubeadm,kubelet和kubectl

  8. 配置yum源(这里使用阿里云的源)
    cat > /etc/yum.repos.d/kubernetes.repo << EOF
    [kubernetes]
    name=Kubernetes
    baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
    enabled=1
    gpgcheck=1
    repo_gpgcheck=1
    gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
    EOF
    yum clean all
    yum makecache
  9. 安装指定版本的kubeadm,kubelet,kubectl
    所有节点 执行
    ## 列出所有版本
    yum list kubelet --showduplicates
    ## 安装指定版本的kubeadm,kubelet,kubectl
    yum install -y kubelet-1.23.6 kubeadm-1.23.6 kubectl-1.23.6
    一安装完就启动是启动不起来的,会报错:
    Sep 05 15:18:34 ddp1 systemd[1]: Started kubelet: The Kubernetes Node Agent.
    -- Subject: Unit kubelet.service has finished start-up
    -- Defined-By: systemd
    -- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
    -- 
    -- Unit kubelet.service has finished starting up.
    -- 
    -- The start-up result is done.
    Sep 05 15:18:34 ddp1 kubelet[15194]: E0905 15:18:34.386967   15194 server.go:205] "Failed to load kubelet config file" err="failed to load Kubelet config file /var/lib/kubelet/config.yaml, error failed to read kubelet config file \"/var/lib/kubelet/config.yaml\", error: open /var/lib/kubelet/config.yaml: no such file or directory" path="/var/lib/kubelet/config.yaml"
    Sep 05 15:18:34 ddp1 systemd[1]: kubelet.service: main process exited, code=exited, status=1/FAILURE
    Sep 05 15:18:34 ddp1 systemd[1]: Unit kubelet.service entered failed state.
    Sep 05 15:18:34 ddp1 systemd[1]: kubelet.service failed.
    根本原因:需要在init后再启动
    解决方案:再init后再启动

启动报错:

journalctl -u kubelet -xe --no-pager
Aug 22 11:42:57 ddp1 kubelet[24473]: I0822 11:42:57.353755   24473 dynamic_cafile_content.go:156] "Starting controller" name="client-ca-bundle::/etc/kubernetes/pki/ca.crt"
Aug 22 11:42:57 ddp1 kubelet[24473]: I0822 11:42:57.397684   24473 server.go:693] "--cgroups-per-qos enabled, but --cgroup-root was not specified.  defaulting to /"
Aug 22 11:42:57 ddp1 kubelet[24473]: E0822 11:42:57.397788   24473 server.go:302] "Failed to run kubelet" err="failed to run Kubelet: running with swap on is not supported, please disable swap! or set --fail-swap-on flag to false. /proc/swaps contained: [Filename\t\t\t\tType\t\tSize\tUsed\tPriority /dev/dm-1                               partition\t8257532\t0\t-2]"
Aug 22 11:42:57 ddp1 systemd[1]: kubelet.service: main process exited, code=exited, status=1/FAILURE
Aug 22 11:42:57 ddp1 systemd[1]: Unit kubelet.service entered failed state.
Aug 22 11:42:57 ddp1 systemd[1]: kubelet.service failed.

问题原因: Kubelet 不支持在开启 Swap 的系统上运行

再次报错:

Aug 22 11:47:03 ddp1 kubelet[26583]: I0822 11:47:03.411512   26583 docker_service.go:264] "Docker Info" dockerInfo=&{ID:5ce8744d-378e-496b-8dd0-25daa6df788c Containers:0 ContainersRunning:0 ContainersPaused:0 ContainersStopped:0 Images:7 Driver:overlay2 DriverStatus:[[Backing Filesystem xfs] [Supports d_type true] [Using metacopy false] [Native Overlay Diff true] [userxattr false]] SystemStatus:[] Plugins:{Volume:[local] Network:[bridge host ipvlan macvlan null overlay] Authorization:[] Log:[awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog]} MemoryLimit:true SwapLimit:true KernelMemory:true KernelMemoryTCP:true CPUCfsPeriod:true CPUCfsQuota:true CPUShares:true CPUSet:true PidsLimit:true IPv4Forwarding:true BridgeNfIptables:true BridgeNfIP6tables:true Debug:false NFd:24 OomKillDisable:true NGoroutines:35 SystemTime:2025-08-22T11:47:03.406858121+08:00 LoggingDriver:json-file CgroupDriver:cgroupfs CgroupVersion:1 NEventsListener:0 KernelVersion:3.10.0-1160.el7.x86_64 OperatingSystem:CentOS Linux 7 (Core) OSVersion:7 OSType:linux Architecture:x86_64 IndexServerAddress:https://index.docker.io/v1/ RegistryConfig:0xc00017c770 NCPU:8 MemTotal:16655888384 GenericResources:[] DockerRootDir:/var/lib/docker HTTPProxy: HTTPSProxy: NoProxy: Name:ddp1 Labels:[] ExperimentalBuild:false ServerVersion:24.0.7 ClusterStore: ClusterAdvertise: Runtimes:map[io.containerd.runc.v2:{Path:runc Args:[] Shim:<nil>} runc:{Path:runc Args:[] Shim:<nil>}] DefaultRuntime:runc Swarm:{NodeID: NodeAddr: LocalNodeState:inactive ControlAvailable:false Error: RemoteManagers:[] Nodes:0 Managers:0 Cluster:<nil> Warnings:[]} LiveRestoreEnabled:false Isolation: InitBinary:docker-init ContainerdCommit:{ID:61f9fd88f79f081d64d6fa3bb1a0dc71ec870523 Expected:61f9fd88f79f081d64d6fa3bb1a0dc71ec870523} RuncCommit:{ID:v1.1.9-0-gccaecfc Expected:v1.1.9-0-gccaecfc} InitCommit:{ID:de40ad0 Expected:de40ad0} SecurityOptions:[name=seccomp,profile=builtin] ProductLicense: DefaultAddressPools:[] Warnings:[]}
Aug 22 11:47:03 ddp1 kubelet[26583]: E0822 11:47:03.411538   26583 server.go:302] "Failed to run kubelet" err="failed to run Kubelet: misconfiguration: kubelet cgroup driver: \"systemd\" is different from docker cgroup driver: \"cgroupfs\""
Aug 22 11:47:03 ddp1 systemd[1]: kubelet.service: main process exited, code=exited, status=1/FAILURE
Aug 22 11:47:03 ddp1 systemd[1]: Unit kubelet.service entered failed state.
Aug 22 11:47:03 ddp1 systemd[1]: kubelet.service failed.

根本原因:Kubelet 的 cgroup 驱动是 “systemd”,但 Docker 容器运行时使用的 cgroup 驱动是 “cgroupfs”,两者不一致,导致 kubelet 拒绝启动!
解决方案:配置Docker使用systemd作为默认Cgroup驱动,配置之后需要重启docker

cat <<EOF > /etc/docker/daemon.json
{
	"registry-mirrors": [
        "http://hub-mirror.c.163.com",
        "https://docker.mirrors.ustc.edu.cn",
        "https://registry.docker-cn.com",
        "https://docker.m.daocloud.io",
        "https://f6qc86pg.mirror.aliyuncs.com"
    ],
	"exec-opts": ["native.cgroupdriver=systemd"]
}
EOF
#重启docker
systemctl restart docker

部署Kubernetes Master节点

在Kubernetes中Master节点是集群的控制节点,它是由三个紧密协作的独立组件组合而成,分别是负责API服务的kube-apiserver、负责调度的kube-scheduler以及负责容器编排的kube-controller-manager,其中整个集群的持久化数据由kube-apiserver处理后保存在Etcd中

  1. master节点初始化
    kubeadm init \
      --kubernetes-version 1.23.6 \
      --apiserver-advertise-address=172.16.7.135 \
      --service-cidr=10.1.0.0/12 \
      --pod-network-cidr=192.168.0.0/16 \
      --control-plane-endpoint=172.16.7.135:6443 \
      --image-repository registry.aliyuncs.com/google_containers \
      --upload-certs
    参数说明:

–kubernetes-version: 指定版本
–image-repository:镜像仓库,离线安装需要把相关镜像先拉取下来,且此处无需再配置
–apiserver-advertise-address:集群通告地址,为通告给其它组件的IP,一般应为master节点的IP地址
–image-repository:由于默认拉取镜像地址k8s.gcr.io国内无法访问,这里指定镜像仓库地址
–kubernetes-version:K8s版本,与上面安装的一致
–service-cidr:集群内部虚拟网络,Pod统一访问入口
–pod-network-cidr:Pod网络,有两个值,打算使用calico网络插件时,最好使用10.96.0.0/16,192.168.0.0/16,打算使用flannel网络插件时最好使用10.244.0.0/16
–control-plane-endpoint: 该参数用于定义一个稳定的访问端点(通常是负载均衡器地址或 DNS 名称),所有工作节点和其他控制平面节点都通过这个端点与集群通信。在 HA 集群中,必须配置。
–upload-certs:自动分发证书供其他控制平面节点加入

注意:

  1. 版本必须和上边安装的kubelet,kubead,kubectl保持一致
  2. kubeadm init 只需要在master上执行即可,worker上不需要执行

初始化过程中可能存在超时错误,此时需修改配置后重新初始化

# 修改系统启动文件
vi /lib/systemd/system/kubelet.service
[Service]
ExecStart=/usr/bin/kubelet --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml

# 需要回退初始化过程后再重新初始化
kubeadm reset

初始化成功日志:

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 172.16.7.135:6443 --token df0fp8.qqn7ph60hnglskol \
        --discovery-token-ca-cert-hash sha256:6230f41413e2cdaf154d7d95b914984a1eb1ddf79d814f166c57156026fd78e2 \
        --control-plane --certificate-key d57db043a3b8e97392bd7415a884ad0fb5c2b9697fafe61416340cca56228b17
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 172.16.7.135:6443 --token df0fp8.qqn7ph60hnglskol \
        --discovery-token-ca-cert-hash sha256:6230f41413e2cdaf154d7d95b914984a1eb1ddf79d814f166c57156026fd78e2 

在master上执行:
如果是普通用户,可以执行:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

如果是root用户,可以执行export KUBECONFIG=/etc/kubernetes/admin.conf

部署worker节点

前提条件:

  1. 安装了kubelet-1.23.6 kubeadm-1.23.6 kubectl-1.23.6
  2. 安装了docker并已启动
  3. 配置Docker使用systemd作为默认Cgroup驱动,配置之后需要重启docker
    kubeadm join 172.16.7.135:6443 --token df0fp8.qqn7ph60hnglskol \
            --discovery-token-ca-cert-hash sha256:6230f41413e2cdaf154d7d95b914984a1eb1ddf79d814f166c57156026fd78e2
    加入成功:
    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.
    在所有worker上执行:
    mkdir -p $HOME/.kube
    scp ddp1:/etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config

    启动kubelet并设置开机自启

master上需要kubeadm init之后才能启动,worker需要kubeadm join之后才能启动

所以这里应该是要最后配置的

systemctl start kubelet
systemctl enable kubelet

每个节点(不管是 Master 还是 Worker)都必须运行 kubelet,否则该节点无法正常工作,Kubernetes 集群也就无法完整运行!

执行完成后查看集群节点,此时均为NotReady状态,待后面网络插件Calico安装完成后,会变成Ready状态

[root@ddp1 ~]# kubectl get nodes
NAME   STATUS     ROLES                  AGE   VERSION
ddp1   NotReady   control-plane,master   45m   v1.23.6
ddp2   NotReady   <none>                 15m   v1.23.6
ddp3   NotReady   <none>                 13m   v1.23.6

后续配置

Master 节点(Control Plane 节点): 默认情况下,Master 节点会被标记为不可调度(unschedulable),即不允许普通 Pod 跑在上面
Worker 节点(Node 节点):负责运行你的 业务 Pod,比如 Deployment、StatefulSet、DaemonSet 创建的容器
Master 节点在初始化时(比如运行 kubeadm init),会自动给它打上一个污点(Taint),用来阻止普通 Pod 调度上去。
在集群资源有限的情况下,我们需要 配置Master节点也作为Node节点,移除Master节点的反亲和性

[root@ddp1 ~]# kubectl taint node --all node-role.kubernetes.io/master-
node/ddp1 untainted
taint "node-role.kubernetes.io/master" not found
taint "node-role.kubernetes.io/master" not found

安装Calico插件

网络插件是必要部件,常用的有Flannel、Calico等。云厂商一般是结合VPC有自己的一套实现。
CNI 全称是“Container Networking Interface”,即容器网络接口,它提供了一种标准的插件机制,用于连接容器到底层网络中。CNI 插件是一种可执行程序,它将实现容器网络连接的一些逻辑打包在一起,允许容器使用不同的网络模型,并提供了一组网络抽象接口。在 Kubernetes 等容器编排平台中,CNI 插件被广泛使用来实现容器网络。
CNI 插件可以由第三方厂商开发和维护,因此,可以选择最适合自己的插件。CNI 插件通常运行在主机上,并由容器运行时调用,例如 Docker、rkt 等。当容器需要连接到主机网络时,CNI 插件将会为其创建必要的网络接口和路由规则。
一些常用的 CNI 插件包括:
Flannel:一个简单易用的网络解决方案,支持多种部署模式。
Calico:一个高度可扩展的容器网络方案,旨在为大规模生产环境提供网络和安全性。
Weave Net:一个分布式的容器网络方案,具有良好的可扩展性和高度自动化的管理。
Cilium:一个基于 eBPF 的容器网络和安全解决方案,提供强大的流量控制和安全性。
安装其中一种网络插件即可。本文使用了 Calico,建议使用 Flannel;使用Calico可能会存在 centos 系统内核版本问题

离线安装:

提前准备好calico的相关images
所有节点执行

cd calico
docker load -i cni-3.25.0.tar
docker load -i node-3.25.0.tar
docker load -i kube-controllers-3.25.0.tar

修改配置文件calico.yaml(安装包中已修改,一般无需再修改)

# 取消如下注释,value修改为kubeadm初始化时--pod-network-cidr参数指定的网段
- name: CALICO_IPV4POOL_CIDR
  value: "192.168.0.0/16"
  
# 找到配置文件的这个位置,在下方添加配置
    - name: CLUSTER_TYPE
      value: "k8s,bgp"
# 在下面添加
    - name: IP_AUTODETECTION_METHOD
      value: "interface=enp0s3"	# enp0s3为本地网卡名

仅在Master节点执行
安装Calico
kubectl apply -f calico.yaml
安装完成,稍等片刻后查看node,可以看到所有节点均为Ready状态

[root@ddp1 calico]# kubectl get node
NAME   STATUS   ROLES                  AGE    VERSION
ddp1   Ready    control-plane,master   179m   v1.23.6
ddp2   Ready    <none>                 148m   v1.23.6
ddp3   Ready    <none>                 147m   v1.23.6

在线安装

wget --no-check-certificate https://docs.projectcalico.org/manifests/calico.yaml
grep image: calico.yaml

docker pull calico/cni:v3.25.0
docker pull calico/node:v3.25.0
docker pull calico/kube-controllers:v3.25.0

修改配置文件calico.yaml

# 取消如下注释,value修改为kubeadm初始化时--pod-network-cidr参数指定的网段
- name: CALICO_IPV4POOL_CIDR
  value: "192.168.0.0/16"

# 找到配置文件的这个位置,在下方添加配置
    - name: CLUSTER_TYPE
      value: "k8s,bgp"
# 在下面添加
    - name: IP_AUTODETECTION_METHOD
      value: "interface=enp0s3"	# enp0s3为本地网卡名

安装Calico
kubectl apply -f calico.yaml
安装完成后,查看node,可以看到所有节点均为Ready状态

[root@ddp1 calico]# kubectl get node
NAME   STATUS   ROLES                  AGE    VERSION
ddp1   Ready    control-plane,master   179m   v1.23.6
ddp2   Ready    <none>                 148m   v1.23.6
ddp3   Ready    <none>                 147m   v1.23.6

查看pod安装情况

查看Kubernetes 系统 容器,需要增加-n kube-system

root@ddp1 calico]# kubectl get pods -n kube-system
NAME                                       READY   STATUS              RESTARTS      AGE
calico-kube-controllers-64cc74d646-bprpr   0/1     ContainerCreating   0             9s
calico-node-cqjw7                          0/1     Running             0             9s
calico-node-n7zzs                          0/1     Running             0             9s
calico-node-t42pq                          0/1     Init:0/3            0             9s
coredns-6d8c4cb4d-78dd7                    1/1     Running             0             177m
coredns-6d8c4cb4d-t8msr                    1/1     Running             0             177m
etcd-ddp1                                  1/1     Running             5 (62m ago)   178m
kube-apiserver-ddp1                        1/1     Running             5 (62m ago)   178m
kube-controller-manager-ddp1               1/1     Running             5 (62m ago)   178m
kube-proxy-4vdmf                           1/1     Running             0             146m
kube-proxy-58jvf                           1/1     Running             1             147m
kube-proxy-jgqs6                           1/1     Running             1 (62m ago)   177m
kube-scheduler-ddp1                        1/1     Running             5 (62m ago)   178m

安装ingress插件

Ingress 是 Kubernetes 中用来管理外部访问集群内服务(比如 HTTP/HTTPS 服务)的 API 对象,它充当 “智能流量入口” 或 “反向代理/路由规则集合”,让你可以通过统一的入口(比如一个域名 + 路径)访问多个不同的 Service,通常搭配 Ingress Controller(如 Nginx、Traefik、HAProxy)一起使用。

在 Kubernetes 中,Ingress 是一种 API 资源对象,它 定义了如何将外部的 HTTP / HTTPS 请求路由到集群内部的 Service,比如:

  • 哪个域名访问哪个服务?
  • 哪个 URL 路径(如 /api、/app)对应哪个后端?
  • 是否启用 HTTPS?
  • 是否做负载均衡、重定向、rewrite 等?
    但它 本身并不提供网络代理功能,它只是 “规则配置”,真正干活的是:

🤖 Ingress Controller(如 Nginx Ingress、Traefik、HAProxy、Caddy 等)

Ingress vs LoadBalancer vs NodePort

方式 说明 适用场景
NodePort 每个 Service 会暴露一个端口(如 30001~32767),通过 : 访问 适合测试,不推荐生产
LoadBalancer 通过云厂商的负载均衡器(如 AWS ALB、GCP LB)暴露服务,自动分配外部 IP 适合云环境,但成本较高,通常用于 TCP/UDP 或单个服务
Ingress 通过 HTTP/HTTPS 协议,基于 域名和路径 智能路由到多个 Service,通常搭配 Nginx 等反向代理 ✅ 最适合 Web 服务、多服务统一入口、生产环境 HTTP(S) 流量管理

离线安装

在所有worker节点加载镜像

# 拉取镜像
docker pull docker pull k8s.gcr.io/ingress-nginx/controller:v1.1.2
docker pull k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1

# 导出镜像(用于离线传输)
docker save -o controller-1.1.2.tar k8s.gcr.io/ingress-nginx/controller:v1.1.2
docker save -o kube-webhook-certgen-1.1.1.tar k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1

# 在目标节点导入
docker load -i controller-1.1.2.tar
docker load -i kube-webhook-certgen-1.1.1.tar

在Master节点修改ingress-nginx-4.0.18.tgz文件

tar -zxf ingress-nginx-4.0.18.tgz
    # externalIPs: []  #修改为如下内容,值为Master节点的IP
    externalIPs:
      - 192.168.204.204
      
      # 注释掉如下两行,否则哈希值检测不通过,无法启动
      #digest: sha256:28b11ce69e57843de44e3db6413e98d09de0f6688e33d4bd384002a44f78405c
      #digest: sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660
tar -zcf ingress-nginx-4.0.18.tgz ingress-nginx

安装ingress

/usr/local/bin/helm install ingress-nginx ingress-nginx-4.0.18.tgz  --namespace ingress-nginx   --create-namespace

配置Ingress网络(暂无需配置)

kubectl apply -f nginx-ingress-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-controller
spec:
  type: NodePort  # 改为 NodePort 或 LoadBalancer(云环境)
  ports:
    - name: http
      port: 80
      targetPort: 80
      nodePort: 30080  # 手动指定 NodePort(可选)
    - name: https
      port: 443
      targetPort: 443
      nodePort: 30443  # 手动指定 NodePort(可选)

K8S安装Mysql

MySQL 是一个有状态、有数据的服务,它的数据需要持久化存储,不能只存在容器内(因为容器可能会被销毁、迁移)。
所以,在 Kubernetes 中运行 MySQL,我们通常会为它创建一个:
PersistentVolume(PV):表示一块持久化存储资源
PersistentVolumeClaim(PVC):表示应用(如 MySQL)对存储的需求
然后通过 StorageClass(SC) 或直接使用 NFS 类型的 PV,将数据保存到比如 NFS、云盘、本地磁盘等存储后端
前提条件:

  1. 1 台 NFS 服务器(可以是集群外的机器,也可以是集群中的某一台节点) ,这台机器上 安装并配置了 NFS 服务端(nfs-server) ,它会 共享出一个目录(比如 /mnt/data/mysql),供 Kubernetes 集群中的节点挂载
  2. Kubernetes 的每个节点(运行 Pod 的机器)都需要安装 NFS 客户端工具,并且正确配置,能够挂载 NFS 共享目录

让 Kubernetes 集群能够根据你的需求(比如通过 PVC 请求存储) 自动为你动态创建持久化存储(PV),而无需你手动预先创建每一个 PV
如果在 Kubernetes 中运行很多应用(比如 MySQL、Redis、应用数据库、日志收集等),它们都需要 持久化存储(PersistentVolume, PV),用来保存数据。

如果使用 静态 PV(手动创建):
需要提前手动为每个可能的存储需求创建 PV(比如大小、存储类型、访问模式等) 然后再创建 PVC 去绑定 ,很麻烦,不灵活,容易浪费或不够用

StorageClass 是 Kubernetes 中的一种 API 资源,它定义了:

  1. “当用户提交 PVC 时,如何动态地创建出对应的 PV(持久化存储)”
  2. 它相当于一个 “存储模板” 或 “存储供应器配置”

安装和挂载NFS

安装NFS服务端

# 安装NFS服务端
yum install -y nfs-utils # 这里所有服务器都要安装

# 定义共享目录及权限
vi /etc/exports
/data/nfs_share 172.16.0.0/16(rw,sync,no_subtree_check,no_root_squash) 
# 格式:<共享目录> <允许的客户端IP/网段>(权限选项)
# 示例:允许 192.168.1.0/24 读写,其他只读
mkdir -p /data/nfs_share
chmod 777 /data/nfs_share
# 启动服务,应用exports配置
systemctl start nfs-server rpcbind  # 这里只需要一台服务器即可
systemctl enable nfs-server rpcbind # 这里只需要一台服务器即可
exportfs -a #加载配置生效 # 这里只需要一台服务器即可

配置NFS客户端验证NFS挂载使用

# 创建本地目录
mkdir /data/nfs
# 挂载远程NFS目录
mkdir -p /data/nfs
mount -t nfs 172.16.7.135:/data/nfs_share /data/nfs
## 验证读写
# 写入数据到本地挂载目录
echo 111 > /data/nfs/test.txt 
# 查看NFS服务端目录
cat /data/nfs_share/test.txt

配置动态SC

安装NFS Provisioner

创建 RBAC 权限
k8s-nfs-provisioner.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 指定一个供应商的名字
# or choose another name, 必须匹配 deployment 的 env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "false" # 删除 PV 的时候,PV 中的内容是否备份
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  namespace: default
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: ccr.ccs.tencentyun.com/gcr-containers/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs-subdir-external-provisioner
            - name: NFS_SERVER
              value: 172.16.7.135 # NFS 服务器的地址
            - name: NFS_PATH
              value: /data/nfs_share # NFS 服务器的共享目录
      volumes:
        - name: nfs-client-root
          nfs:
            server: 172.16.7.135
            path: /data/nfs_share
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: default
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

安装k8s-nfs-provisioner.yaml

# 安装
kubectl apply -f  k8s-nfs-provisioner.yaml
# 验证 StorageClass
kubectl get storageclass

K8S安装Mysql

vi mysql-k8s.yaml

---
# 1. MySQL Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: digiwin@123
          ports:
            - containerPort: 3307
          volumeMounts:
            - name: mysql-persistent-storage
              mountPath: /var/lib/mysql
      volumes:
        - name: mysql-persistent-storage
          persistentVolumeClaim:
            claimName: mysql-pvc
---
# 2. MySQL PVC(显式使用 nfs-client 存储类)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs-client  # 使用你部署的动态 NFS 存储类
  resources:
    requests:
      storage: 1Gi
---
# 3. MySQL Service(ClusterIP,集群内访问)
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  type: ClusterIP
  selector:
    app: mysql
  ports:
    - name: mysql
      port: 3307
      targetPort: 3307

安装k8s-nfs-provisioner.yaml

# 安装
kubectl apply -f  mysql-k8s.yaml

启动后报错:

[root@ddp1 ~]# kubectl get pods
NAME                                     READY   STATUS         RESTARTS   AGE
mysql-deployment-557f98d759-4qzrp        0/1     ErrImagePull   0          23s
nfs-client-provisioner-7f44f49d7-2ldbk   1/1     Running        0          99m

是因为docker 拉取镜像有问题,修改docker镜像配置,然后在3台服务器上都提前手动拉取mysql的镜像

vim /etc/docker/daemon.json 
{
"registry-mirrors": [
                "https://do.nark.eu.org",
                "https://dc.j8.work",
                "https://docker.m.daocloud.io",
                "https://dockerproxy.com",
                "https://docker.mirrors.ustc.edu.cn",
                "https://docker.nju.edu.cn"
    ],

"exec-opts": ["native.cgroupdriver=systemd"]
}
systemctl daemon-reload
systemctl restart docker
docker pull mysql:8.0

重新执行命令:

kubectl apply -f  mysql-k8s.yaml

还是报错:

[root@ddp1 ~]# kubectl get pods
NAME                                     READY   STATUS             RESTARTS        AGE
mysql-deployment-587b8dc4d8-vcfsd        0/1     CrashLoopBackOff   1 (7s ago)      11s
nfs-client-provisioner-7f44f49d7-2ldbk   1/1     Running            2 (4m35s ago)   165m
[root@ddp1 ~]# kubectl describe pod mysql-deployment-587b8dc4d8-vcfsd
Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  71s                default-scheduler  0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims.
  Normal   Scheduled         70s                default-scheduler  Successfully assigned default/mysql-deployment-587b8dc4d8-vcfsd to ddp2
  Normal   Pulled            27s (x4 over 69s)  kubelet            Container image "mysql:8.0" already present on machine
  Normal   Created           26s (x4 over 69s)  kubelet            Created container mysql
  Normal   Started           26s (x4 over 69s)  kubelet            Started container mysql
  Warning  BackOff           12s (x6 over 67s)  kubelet            Back-off restarting failed container

kubectl logs mysql-deployment-7f44f49d7-2ldbk 
2025-08-25 06:43:38+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.43-1.el9 started.
chown: changing ownership of '/var/lib/mysql/': Operation not permitted
chown: changing ownership of '/var/lib/mysql': Operation not permitted

这是因为NFS 与 Kubernetes 挂载时涉及的权限问题
Kubernetes 挂载 NFS 类型的 PV(通过 PVC)时:

  • 使用的是 动态存储 Provisioner(如 nfs-subdir-external-provisioner)
  • 它会在你指定的 NFS 服务端共享目录(比如 /data/nfs_share)下,动态创建一个子目录(例如:/data/nfs_share/default-mysql-pvc-xxxxx)
  • 然后 Kubernetes 会把这个子目录作为 PV,挂载到容器内的 /var/lib/mysql
  • *表面上:** 是 Kubernetes Pod 中的 mysql 容器(通常是 root 用户启动,或以 mysql 用户运行)
  • *实际上:** 是该容器通过 NFS 挂载访问服务端的共享目录
  • *关键点:**
    NFS 服务端会对访问的用户进行 UID/GID 映射,如果 NFS 服务端启用了 root_squash(默认通常是启用的!),那么: 来自客户端的 root 用户(UID 0),在访问 NFS 共享目录时,会被映射为匿名用户(通常是 nobody 或 nfsnobody,UID 65534 或类似),没有写权限!

这就导致:
即使你在容器中是 root,在 NFS 服务端看来,你可能只是个没有权限的匿名用户
所以你无法在 NFS 目录中创建文件、修改属主、初始化 MySQL 数据等操作!

因此,若使用 NFS 作为持久化存储,需检查 NFS 服务端配置,确保添加 no_root_squash 选项以允许 root 用户操作共享目录
5。修改后需重启 NFS 服务

vim /etc/exports
/data/nfs_share 172.16.0.0/16(rw,sync,no_subtree_check,no_root_squash) # 增加no_root_squash
systemctl restart rpcbind && systemctl restart nfs

再次重新安装:

kubectl delete -f  mysql-k8s.yaml 
kubectl apply -f  mysql-k8s.yaml 
[root@ddp1 ~]# kubectl get pods
NAME                                     READY   STATUS    RESTARTS      AGE
mysql-deployment-587b8dc4d8-xdccx        1/1     Running   0             13m
nfs-client-provisioner-7f44f49d7-2ldbk   1/1     Running   2 (35m ago)   3h16m

到此,服务正常启动了
但是通过telnet无法连通mysql,因为Service 是 ClusterIP 类型,只能被集群内的其他 Pod 访问,如果想要在容器外被访问,需要使用NodePort

查看集群有哪些 Service

[root@ddp1 ~]# kubectl get svc
NAME            TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
kubernetes      ClusterIP   10.0.0.1      <none>        443/TCP    3d
mysql-service   ClusterIP   10.8.235.95   <none>        3307/TCP   16m

可以看到是ClusterIP类型

可以进入容器内去操作mysql:

kubectl exec -it mysql-deployment-587b8dc4d8-xdccx -- bash

至此,mysql服务可以正常使用了

可以参考一下Centos7 安装部署Kubernetes(k8s)集群