Kubernetes学习指北之单节点集群(Master)

一、 单节点集群(Single Node Cluster)说明

学习和开发利器,操作简单

特点

  • Master 节点和 Worker 节点在同一台机器上
    • kube-apiserver、kube-scheduler、kube-controller-manager、kubelet 都在同一台机器
    • Pod 也在同一台机器上运行
  • 资源有限:所有容器共享同一台机器的 CPU、内存和存储
  • 高可用性低:Master 节点宕机,整个集群不可用
  • 网络简单:只需一个节点,Pod 网络配置相对简单

适用场景

  • 学习和测试 Kubernetes 核心概念
  • 开发环境部署微服务
  • 练习 kubectl 命令、Deployment、Service 等

优点

  • 配置简单,一条命令就能搭建

  • 资源占用低,不需要多台虚拟机

  • 部署快速,便于练手

缺点

  • 不适合生产环境

  • 没有高可用性

  • 无法模拟真实多节点调度

二、 准备环境和工具

  1. 这里我使用的CentOS7
  2. 做一些初始化
    # 切换 root
    sudo -i
    
    # 关闭防火墙(实验环境)
    systemctl stop firewalld
    systemctl disable firewalld
    
    # 关闭 selinux
    setenforce 0
    sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config
    
    # 关闭 swap
    swapoff -a
    sed -i '/swap/d' /etc/fstab
    
    # 调整内核参数(K8s 网络需要)
    cat <<EOF > /etc/sysctl.d/k8s.conf
    net.bridge.bridge-nf-call-ip6tables = 1
    net.bridge.bridge-nf-call-iptables = 1
    EOF
    sysctl --system
    

三 、 安装 Docker 或 Containerd

Kubernetes 需要一个容器运行时,这里推荐 containerd(更接近 K8s 原生)。

# 安装 containerd
yum install -y yum-utils device-mapper-persistent-data lvm2

# 添加 Docker CE 源(阿里云加速)
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 更新缓存
yum makecache fast

# 安装 containerd
yum install -y containerd.io

# 生成默认配置
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml

# 修改 SystemdCgroup = true(K8s 要求)
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# 启动并开机自启
systemctl enable --now containerd
systemctl status containerd

如果 yum install -y yum-utils device-mapper-persistent-data lvm2出现Could not retrieve mirrorlist http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=os&infra=stock error was 14: curl#6 - “Could not resolve host: mirrorlist.centos.org; Unknown error” 这类问题需要切换到可用镜像源

  1. 替换 CentOS 镜像源
    # 备份旧的 repo 文件
    sudo mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
    
    # 使用 vault 源(CentOS 官方归档)
    cat <<EOF | sudo tee /etc/yum.repos.d/CentOS-Base.repo
    [base]
    name=CentOS-7 - Base
    baseurl=http://vault.centos.org/7.9.2009/os/\$basearch/
    gpgcheck=0
    enabled=1
    
    [updates]
    name=CentOS-7 - Updates
    baseurl=http://vault.centos.org/7.9.2009/updates/\$basearch/
    gpgcheck=0
    enabled=1
    
    [extras]
    name=CentOS-7 - Extras
    baseurl=http://vault.centos.org/7.9.2009/extras/\$basearch/
    gpgcheck=0
    enabled=1
    EOF
    
    # 清理缓存并更新
    yum clean all
    yum makecache
    
  2. 使用阿里云镜像(更快,推荐国内用户)
    # 下载阿里云 repo 文件
    sudo curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    
    # 清理缓存并更新
    yum clean all
    yum makecache
    
  3. 检查 DNS(如果还报 Could not resolve host)
    1. 说明虚拟机网络没配置好,先检查 DNS:cat /etc/resolv.conf
    2. 如果里面没有:
      nameserver 8.8.8.8
      nameserver 114.114.114.114
      
    3. 就加上:
      echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf
      echo "nameserver 114.114.114.114" | sudo tee -a /etc/resolv.conf
      

四、安装 Kubernetes 工具

安装 kubeadm、kubelet、kubectl。

# 添加 Kubernetes 仓库
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
repo_gpgcheck=0
EOF

# 安装
yum install -y kubelet kubeadm kubectl

# 开机自启
systemctl enable kubelet

五、初始化单节点集群

# 使用 kubeadm 初始化(--pod-network-cidr 根据网络插件而定)
kubeadm init --apiserver-advertise-address=192.168.56.10 --pod-network-cidr=10.244.0.0/16

# 如果出现 /proc/sys/net/bridge/bridge-nf-call-iptables does not exist 和 /proc/sys/net/ipv4/ip_forward contents are not set to 1 执行下面的命令解决然后重新初始化:
modprobe br_netfilter
echo "br_netfilter" >> /etc/modules-load.d/k8s.conf
#配置内核参数
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# 应用配置
sysctl --system
#验证
lsmod | grep br_netfilter
cat /proc/sys/net/bridge/bridge-nf-call-iptables
cat /proc/sys/net/ipv4/ip_forward
# 期望输出:
# br_netfilter 模块存在
# bridge-nf-call-iptables = 1
# ip_forward = 1


# 清理上一次初始化的残留
kubeadm reset -f   # 清理上次失败的初始化
rm -rf /etc/cni/net.d # 清理 CNI 网络配置
# 清理 iptables(避免残留规则)
iptables -F
iptables -t nat -F
iptables -t mangle -F
iptables -X
# 清理 kubeconfig
rm -rf $HOME/.kube

# 重新初始化
kubeadm init --apiserver-advertise-address=192.168.56.10 --pod-network-cidr=10.244.0.0/16

# 如果出现拉取镜像失败问题,核心原因就是 虚拟机里根本访问不到 Google 的镜像仓库(k8s.gcr.io / registry.k8s.io),给 kubeadm 用国内的镜像源,指定 image-repository 重新初始化
# 阿里云: registry.aliyuncs.com/google_containers
# Azure China: gcr.azk8s.cn/google_containers
# : quay-mirror.qiniu.com
kubeadm init --apiserver-advertise-address=192.168.56.10 --pod-network-cidr=10.244.0.0/16 --image-repository=registry.aliyuncs.com/google_containers

# 如果出现 The kubelet is not running
#        - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)  
# 这是 CentOS7 + kubelet 常见问题,主要原因是 cgroup 驱动没配置好 或 containerd 默认配置不对。
# 1. 检查 kubelet 状态
systemctl status kubelet -l
journalctl -xeu kubelet
# 大概率会看到类似:
# failed to run Kubelet: failed to run Kubelet: misconfiguration: kubelet cgroup driver
# 2. 修改 containerd 配置, 生成 containerd 默认配置:
containerd config default > /etc/containerd/config.toml
# 3. 编辑/etc/containerd/config.toml, 把 sandbox 镜像 改成阿里云 pause 镜像:
[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"
# 4. 找到 SystemdCgroup 配置(默认是 false),改成:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
  SystemdCgroup = true
# 5. 重启 containerd & kubelet
systemctl daemon-reexec
systemctl restart containerd
systemctl restart kubelet
# 6. 清理并再次执行 kubeadm init
sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d
sudo rm -rf /var/lib/etcd
sudo rm -rf /etc/kubernetes/manifests/*
sudo lsof -i :6443,10250,10259,10257,2379,2380
# 如果有进程占用,可以 kill
sudo systemctl restart kubelet

kubeadm init --apiserver-advertise-address=192.168.56.10 --pod-network-cidr=10.244.0.0/16 --image-repository=registry.aliyuncs.com/google_containers

执行成功后,会输出一段提示,包括:

  • Your Kubernetes control-plane has initialized successfully!
  • To start using your cluster, you need to run the following as a regular user: 配置 kubectl 的命令
  • kubeadm join ...(多节点才用到)

按照提示执行如:

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

六、安装网络插件(CNI)

最简单用 Flannel:

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 如果访问不了,可以改成使用 阿里云加速的镜像
# 先下载原始 yaml:
curl -O https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

# 替换国内镜像
sed -i 's#ghcr.io/flannel-io/flannel#registry.aliyuncs.com/google_containers/flannel#g' kube-flannel.yml

# 应用 Flannel
kubectl apply -f kube-flannel.yml

# 检查 Flannel Pod 状态
kubectl get pods -n kube-system -l app=flannel -w
# 等待 Pod 变为 Running
# 如果一直 Pending,可能是:
   #节点有 NoSchedule 污点(需要移除)
   #镜像拉取失败(网络或镜像源问题)

# 移除单节点控制平面的污点。 单节点控制平面默认不调度 Pod,需要允许调度:
kubectl taint nodes k8s-single node-role.kubernetes.io/control-plane-

# 再次查看节点状态
kubectl get nodes
# 节点 STATUS 应该从 NotReady 变为 Ready
# 网络插件就绪后,CoreDNS Pod 也会从 Pending 变为 Running

# 检查 CoreDNS
kubectl get pods -n kube-system -l k8s-app=kube-dns
# 一旦网络就绪,CoreDNS 应该变为 Running
# 这是集群 DNS 功能是否正常的关键

七、允许单节点调度 Pod

默认 Master 节点不调度 Pod(但我们单机需要)。

kubectl taint nodes --all node-role.kubernetes.io/control-plane-

八、验证集群

# 查看节点状态
kubectl get nodes -w

# 查看 Pod 状态
kubectl get pods -A
kubectl get pods -n kube-system

如果节点是 Ready,Pods 都是 Running,就说明单节点集群搭建完成

#如果节点是NotReady,说明 控制平面节点已经初始化,但网络插件(CNI)还没完全就绪。
#查看 kube-system Pod 状态
kubectl get pods -n kube-system
# 重点看 kube-flannel Pod 是否 Running。
#如果 Pod Pending 或 CrashLoopBackOff,节点就会 NotReady。

单节点 Kubernetes 就绪操作顺序

# 1️⃣ 移除控制平面污点,允许单节点调度 Pod
kubectl taint nodes k8s-single node-role.kubernetes.io/control-plane-

# 2️⃣ 删除旧的 Flannel 配置(可选,保证干净环境)
kubectl delete -f kube-flannel.yml

# 3️⃣ 重新应用 Flannel CNI
kubectl apply -f kube-flannel.yml

# 4️⃣ 等待 Flannel Pod 启动
kubectl get pods -n kube-system -l app=flannel -w
# 等 Pod 全部状态变为 Running 后,Ctrl+C 退出

# 5️⃣ 检查节点状态
kubectl get nodes -o wide
# 节点 STATUS 应该变为 Ready

# 6️⃣ 检查 CoreDNS 状态
kubectl get pods -n kube-system -l k8s-app=kube-dns
# CoreDNS Pod 应变为 Running

# 7️⃣ 测试 Pod 调度
kubectl run nginx --image=nginx
kubectl get pods
# Pod 应成功调度到单节点

其他问题:

systemctl status kubelet -l

  1. Flannel 的 CNI 插件 Pod 拉取镜像失败
    1. 手动拉取镜像到节点
      1. ctr image pull registry.aliyuncs.com/google_containers/flannel:v0.27.3
      2. ctr image pull registry.aliyuncs.com/google_containers/flannel-cni-plugin:v1.7.1-flannel1
    2. 标记(Tag)镜像,让 Kubernetes 能找到。Kubelet/Flannel 期望的镜像名必须和 kube-flannel.yml 中一致:
      1. ctr images tag registry.aliyuncs.com/google_containers/flannel:v0.27.3 ghcr.io/flannel-io/flannel:v0.27.3
      2. ctr images tag registry.aliyuncs.com/google_containers/flannel-cni-plugin:v1.7.1-flannel1 registry.aliyuncs.com/google_containers/flannel-cni-plugin:v1.7.1-flannel1
    3. 然后重新应用 Flannel:
      kubectl delete -f kube-flannel.yml
      kubectl apply -f kube-flannel.yml
      
  2. 本地下载docker镜像,虚拟机导入
# 本机下载导出
docker save ghcr.io/flannel-io/flannel:v0.27.3 -o ./data/flannel.tar
docker save ghcr.io/flannel-io/flannel-cni-plugin:v1.7.1-flannel1 -o ./data/flannel-cni-plugin.tar

# 修改Vagrantfile共享目录
config.vm.synced_folder "./images", "/home/vagrant/flannel-images"
#重新挂载(虚拟机已经启动,可以直接 vagrant reload
vagrant reload

#重新ssh,切换root
vagrant ssh k8s-single -- -F NUL
sudo -i
# 虚拟机里访问共享目录:
ls /home/vagrant/flannel-images
sudo ctr -n k8s.io images import /home/vagrant/flannel-images/flannel.tar
sudo ctr -n k8s.io images import /home/vagrant/flannel-images/flannel-cni-plugin.tar

#验证
sudo ctr -n k8s.io images import /home/vagrant/flannel-images/flannel-cni-plugin.tar

# 修改 kube-flannel 主镜像
sed -i 's|image: registry.aliyuncs.com/google_containers/flannel:v0.*|image: ghcr.io/flannel-io/flannel:v0.27.3|' kube-flannel.yml

# 修改 cni 插件镜像
sed -i 's|image: registry.aliyuncs.com/google_containers/flannel-cni-plugin:v1.*|image: ghcr.io/flannel-io/flannel-cni-plugin:v1.7.1-flannel1|' kube-flannel.yml

# 然后重新应用 Flannel:
kubectl delete -f kube-flannel.yml
kubectl apply -f kube-flannel.yml
# 等待 Flannel Pod 启动
kubectl get pods -n kube-system -l app=flannel -

kubectl get nodes
# 单节点应该从 NotReady 变成 Ready。

# 如果 Flannel 容器 Running,但节点仍 NotReady,说明 kubelet 还没认到 CNI,可能需要重启 kubelet:
sudo systemctl restart kubelet

# 然后再观察状态:
kubectl get nodes -w

九、 测试 Pod 调度

# 查看系统命名空间的 Pod 状态
kubectl get pods -n kube-system

# 部署一个测试 Pod 验证网络:
kubectl run nginx-test --image=nginx --restart=Never
kubectl get pod nginx-test -o wide
kubectl get pods

# Pod IP 和节点 IP 是否在 Flannel 网段内(默认 10.244.0.0/
kubectl get pod nginx-test -o wide
  • 确认 Pod 能调度到单节点
  • 如果能运行,说明单节点 Kubernetes 已完全可用

十、Laravel on Kubernetes 单节点示例

  1. 准备 Laravel 项目镜像,

    1. Laravel 项目目录写一个 Dockerfile,示例如下:
      FROM php:8.3-fpm
      
      # 安装 SQLite 和 Laravel 需要的扩展
      RUN apt-get update && apt-get install -y libsqlite3-dev \
         && docker-php-ext-install pdo pdo_sqlite bcmath
      
      # 安装 Composer
      COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
      
      WORKDIR /var/www/html
      
      # 拷贝代码
      COPY . .
      
      # 安装依赖(忽略 dev)
      RUN composer install --no-dev --optimize-autoloader \
      && chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
      
    2. 构建镜像(假设名字叫 laravel-app:demo)
      docker build -t laravel-app:demo .
      
    3. 加载到 containerd(因为 kubelet 默认用 containerd 而不是 docker):
      # 保存镜像
      docker save laravel-app:demo -o laravel-app.tar
      
      # 导入到 containerd
      sudo ctr -n k8s.io images import laravel-app.tar
      
  2. Kubernetes YAML(最小示例)

    创建一个 laravel-demo.yaml:

    Secret 管理 MySQL 如:

    # 会生成一个 Secret(存储在 etcd,K8s 内部是 base64 编码的,不是明文,但还是要注意权限管理)
    kubectl create secret generic mysql-secret \
    --from-literal=username=laravel \
    --from-literal=password=SuperSecret123 \
    --from-literal=database=laravel_db \
    --from-literal=host=mysql-service
    
    laravel-demo.yaml示例
    
    # ==========================
    # Namespace,所有资源都放在 laravel-demo 命名空间
    apiVersion: v1
    kind: Namespace
    metadata:
    name: laravel-demo
    
    ---
    # ==========================
    # MySQL Secret
    # 用来存储数据库连接信息
    # 假设之前已经用 kubectl create secret 创建了,也可以在 YAML 中声明
    # ==========================
    apiVersion: v1
    kind: Secret
    metadata:
    name: mysql-secret
    namespace: laravel-demo
    type: Opaque
    # 数据可以省略,已经创建过 secret
    
    ---
    # ==========================
    # PHP-FPM Deployment
    # 运行 Laravel 的 PHP 后端
    # ==========================
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: laravel-php
    namespace: laravel-demo
    spec:
    replicas: 1  # 单节点只需一个副本
    selector:
       matchLabels:
          app: laravel-php
    template:
       metadata:
          labels:
          app: laravel-php
       spec:
          containers:
          - name: php-fpm
             image: php:8.3-fpm
             ports:
                - containerPort: 9000  # PHP-FPM 默认端口
             env:  # 从 Secret 中读取数据库信息
                - name: DB_USERNAME
                valueFrom:
                   secretKeyRef:
                      name: mysql-secret
                      key: username
                - name: DB_PASSWORD
                valueFrom:
                   secretKeyRef:
                      name: mysql-secret
                      key: password
                - name: DB_DATABASE
                valueFrom:
                   secretKeyRef:
                      name: mysql-secret
                      key: database
                - name: DB_HOST
                valueFrom:
                   secretKeyRef:
                      name: mysql-secret
                      key: host
             volumeMounts:
                - name: laravel-code
                mountPath: /var/www/html  # Laravel 项目挂载路径
          volumes:
          - name: laravel-code
             hostPath:  # 挂载宿主机代码目录
                path: /home/vagrant/laravel-demo
                type: Directory
    
    ---
    # ==========================
    # Nginx Deployment
    # 处理 HTTP 请求,反向代理到 PHP-FPM
    # ==========================
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: laravel-nginx
    namespace: laravel-demo
    spec:
    replicas: 1
    selector:
       matchLabels:
          app: laravel-nginx
    template:
       metadata:
          labels:
          app: laravel-nginx
       spec:
          containers:
          - name: nginx
             image: nginx:1.25
             ports:
                - containerPort: 80  # HTTP 默认端口
             volumeMounts:
                - name: laravel-code
                mountPath: /var/www/html  # Laravel 项目代码挂载
                - name: nginx-conf
                mountPath: /etc/nginx/conf.d  # Nginx 配置挂载
          volumes:
          - name: laravel-code
             hostPath:
                path: /home/vagrant/laravel-demo
                type: Directory
          - name: nginx-conf
             configMap:
                name: nginx-config  # 配置来自 ConfigMap
    
    ---
    # ==========================
    # Nginx 配置 ConfigMap
    # 配置 Nginx 反向代理到 PHP-FPM
    # ==========================
    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: nginx-config
    namespace: laravel-demo
    data:
    default.conf: |
       server {
          listen 80;
          server_name localhost;
          root /var/www/html/public;  # Laravel 公共目录
    
          index index.php index.html;
    
          location / {
                try_files $uri $uri/ /index.php?$query_string;
          }
    
          location ~ \.php$ {
                fastcgi_pass laravel-php:9000;  # PHP-FPM 服务名和端口
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          }
       }
    
    ---
    # ==========================
    # Nginx Service
    # 暴露 HTTP 端口,NodePort 用于单节点测试
    # ==========================
    apiVersion: v1
    kind: Service
    metadata:
    name: laravel-nginx-service
    namespace: laravel-demo
    spec:
    selector:
       app: laravel-nginx  # 关联 Nginx Pod
    ports:
       - protocol: TCP
          port: 80
          targetPort: 80
    type: NodePort  # 可以通过节点 IP:NodePort 访问
    
  3. 部署 & 使用

# 部署
kubectl apply -f laravel-demo.yaml

# 查看 Pod 状态:
kubectl get pods

等待 laravel-app 和 laravel-nginx 都是 Running。

然后在浏览器访问:

http://<你的宿主机IP>:30080

就能打开 Laravel 的首页

  1. 管理
    1. 应用生命周期管理

      # 查看运行状态
      kubectl get pods
      
      # 查看详细日志(Laravel PHP-FPM Pod)
      kubectl logs -f <laravel-app-pod> -c laravel
      
      # 查看 Nginx 的日志
      kubectl logs -f <laravel-nginx-pod> -c nginx
      
      # 重启 Deployment(相当于重新部署)
      kubectl rollout restart deployment laravel-app
      kubectl rollout restart deployment laravel-nginx
      
    2. 扩展和升级

      Kubernetes 会 滚动更新,保证不中断服务。

      # 扩容(增加副本数):
      kubectl scale deployment laravel-app --replicas=3
      kubectl scale deployment laravel-nginx --replicas=2
      
      # 升级镜像(比如新版本 Laravel 代码):
      kubectl set image deployment/laravel-app laravel=laravel-app:v2
      
    3. 监控与调试

      # 实时监控 Pod:
      kubectl get pods -w
      
      # 进入容器调试:
      kubectl exec -it <laravel-app-pod> -- bash
      

使用虚拟机说明

Vagrantfile 单机示例

Vagrant.configure("2") do |config|
  config.vm.box = "generic/centos7"

  config.vm.define "k8s-single" do |vm|
    vm.vm.hostname = "k8s-single"
    vm.vm.network "private_network", ip: "192.168.56.10"

    vm.vm.provider "virtualbox" do |vb|
      vb.memory = 2048
      vb.cpus = 2
      vb.name = "vagrant-k8s-single"
    end
  end
end

vagrant 命令:

vagrant up

vagrant status k8s-single

vagrant ssh k8s-single -- -F NUL

vagrant halt k8s-single
# 删除虚拟机
vagrant destroy k8s-single