手工建集群到全自动发布:k8s集群部署与DevOps流水线整合实战

手工建集群到全自动发布:k8s集群部署与DevOps流水线整合实战

文章目录

  • 手工建集群到全自动发布:k8s集群部署与DevOps流水线整合实战
    • 一、整体架构与实验环境
      • [1.1 最终形态:一条打通 Git、Jenkins、Harbor 与 K8s 的流水线](#1.1 最终形态:一条打通 Git、Jenkins、Harbor 与 K8s 的流水线)
      • [1.2 两套实验环境](#1.2 两套实验环境)
    • [二、手工部署:kubeadm + Docker 搭建 K8s 1.28 集群](#二、手工部署:kubeadm + Docker 搭建 K8s 1.28 集群)
      • [2.1 主机基础配置](#2.1 主机基础配置)
      • [2.2 系统级准备:防火墙、swap、内核参数与 IPVS](#2.2 系统级准备:防火墙、swap、内核参数与 IPVS)
      • [2.3 安装 Docker CE 并配置镜像加速](#2.3 安装 Docker CE 并配置镜像加速)
      • [2.4 安装 cri-dockerd:让 Docker 重新接上 K8s](#2.4 安装 cri-dockerd:让 Docker 重新接上 K8s)
      • [2.5 安装 kubeadm / kubelet / kubectl](#2.5 安装 kubeadm / kubelet / kubectl)
      • [2.6 初始化集群(只在 master 节点操作)](#2.6 初始化集群(只在 master 节点操作))
      • [2.7 配置 kubectl 并检查控制平面](#2.7 配置 kubectl 并检查控制平面)
      • [2.8 worker 节点加入集群(只在 node 节点操作)](#2.8 worker 节点加入集群(只在 node 节点操作))
      • [2.9 部署 Calico 网络插件](#2.9 部署 Calico 网络插件)
      • [2.10 验证集群](#2.10 验证集群)
      • [2.11 优化与冒烟测试](#2.11 优化与冒烟测试)
    • [三、快速部署:Kuboard 脚本安装 K8s 1.19(整合环境)](#三、快速部署:Kuboard 脚本安装 K8s 1.19(整合环境))
      • [3.1 前置环境](#3.1 前置环境)
      • [3.2 安装 Docker 及 kubelet(所有节点)](#3.2 安装 Docker 及 kubelet(所有节点))
      • [3.3 初始化 master 节点](#3.3 初始化 master 节点)
      • [3.4 初始化 worker 节点](#3.4 初始化 worker 节点)
    • [四、承上启下:单机 Docker 部署阶段的闭环(上期彩蛋)](#四、承上启下:单机 Docker 部署阶段的闭环(上期彩蛋))
    • [五、Kubernetes 与 DevOps 整合](#五、Kubernetes 与 DevOps 整合)
      • [5.1 准备部署 YAML 并发布到 GitLab](#5.1 准备部署 YAML 并发布到 GitLab)
      • [5.2 对接 Harbor 私有仓库](#5.2 对接 Harbor 私有仓库)
      • [5.3 配置 Jenkins 向 K8s master 推送 YAML](#5.3 配置 Jenkins 向 K8s master 推送 YAML)
      • [5.4 Jenkins 免密 SSH 登录 K8s master](#5.4 Jenkins 免密 SSH 登录 K8s master)
      • [5.5 在流水线中增加"远程执行 kubectl"阶段](#5.5 在流水线中增加"远程执行 kubectl"阶段)
      • [5.6 触发构建并验证发布结果](#5.6 触发构建并验证发布结果)
    • 六、踩坑清单与经验小结

一、整体架构与实验环境

1.1 最终形态:一条打通 Git、Jenkins、Harbor 与 K8s 的流水线

完整交付链路如下图所示:开发者 push 代码到 GitLab(GitLab / GitHub / Gitee 均可),Jenkins 按 tag 拉取代码,依次完成 Maven 构建、SonarQube 质量检测、Docker 制作自定义镜像并推送到 Harbor;随后把部署用的 YAML 文件推送到 K8s master 节点,并远程执行 kubectl apply 完成发布;构建结果通过钉钉机器人通知到群。

图 1:整合后的整体流程------代码 push 到 GitLab,Jenkins 负责构建、质检、制镜像、推 YAML,K8s 集群负责运行,钉钉负责通知。

1.2 两套实验环境

笔记中先后用到了两套环境,读者不要混淆:

环境 A:kubeadm 手工部署 K8s 1.28(三节点)

CPU 内存 硬盘 IP 主机名 软件
2 核 4G 200G 192.168.100.128 master docker
4 核 6G 400G 192.168.100.129 node1 docker
4 核 6G 400G 192.168.100.136 node2 docker

环境 B:DevOps 整合环境(Kuboard 脚本快速部署 K8s 1.19)

IP 主机名 角色
192.168.100.145 master K8s master,同时作为 YAML 推送目标
192.168.100.146 node1 K8s worker
192.168.100.154:80 harbor 私有镜像仓库(笔记中个别命令写作 192.168.100.145:80,实际操作请以自己的 Harbor 地址统一为准)

此外还有既有的 Jenkins 服务器(容器化部署)与 GitLab 服务器,属于前期 DevOps 章节的成果,本文直接复用。


二、手工部署:kubeadm + Docker 搭建 K8s 1.28 集群

Kubernetes 1.24 之后官方移除了对原生 Docker 的支持(dockershim),但 Docker 作为最熟悉的容器运行时,仍然可以通过 cri-dockerd 这座桥继续为 K8s 服务。本节就是这条路线的完整记录。

2.1 主机基础配置

在三台主机中各自设置对应主机名:

bash 复制代码
# hostnamectl set-hostname master
# hostnamectl set-hostname node1
# hostnamectl set-hostname node2

所有节点配置主机名解析并测试 ping 通:

bash 复制代码
[root@master ~]# vim /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.100.128 master
192.168.100.129 node1
192.168.100.136 node2

安装环境包:

bash 复制代码
[root@master ~]# yum -y install vim lrzsz unzip wget net-tools tree bash-completion \
  conntrack ntpdate ntp ipvsadm ipset iptables curl sysstat libseccomp git psmisc \
  telnet unzip gcc gcc-c++ make

这些包按用途可以分成几类:

类别 软件包 核心功能
文件操作 vim, lrzsz, unzip 编辑 / 上传下载 / 解压 ZIP
网络管理 net-tools, ipvsadm 网络配置 / IPVS 负载均衡
系统监控 sysstat, psmisc 性能监控 / 进程管理
开发编译 gcc, make 代码编译 / 自动化构建
安全防护 iptables, libseccomp 防火墙 / 容器系统调用限制

2.2 系统级准备:防火墙、swap、内核参数与 IPVS

关闭防火墙和内核安全机制:

bash 复制代码
[root@master ~]# systemctl disable firewalld --now
[root@master ~]# sed -i 's/enforcing/disabled/g' /etc/selinux/config
[root@master ~]# setenforce 0

关闭 swap 分区。启用 swap 会对系统性能产生非常负面的影响,因此 Kubernetes 要求每个节点都禁用 swap:

bash 复制代码
[root@master ~]# swapoff -a && sed -i '/swap/s/^/#/' /etc/fstab

关闭 NetworkManager(可选):

bash 复制代码
[root@master ~]# systemctl stop NetworkManager
[root@master ~]# systemctl disable NetworkManager

调整系统内核参数(注意:先升级内核):

bash 复制代码
[root@master ~]# yum update -y kernel && reboot

[root@master ~]# cat > /etc/sysctl.d/kubernetes.conf <<EOF
# 开启 Linux 内核的网络桥接功能,同时启用 iptables 和 ip6tables 的网络包过滤功能
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
# 开启路由转发,转发 IPv4 的数据包
net.ipv4.ip_forward=1
# 尽可能避免使用交换分区,提升 k8s 性能
vm.swappiness=0
# 不检查物理内存是否够用
vm.overcommit_memory=1
EOF

# 立即生效
[root@master ~]# sysctl --system

调整 Linux 资源限制(可选)。Linux 默认一个进程可以打开的最大文件句柄数是 1024,集群场景下远远不够:

bash 复制代码
[root@master ~]# ulimit -SHn 65535

[root@master ~]# cat >> /etc/security/limits.conf <<EOF
* soft nofile 655360
* hard nofile 131072
* soft nproc 655350
* hard nproc 655350
* soft memlock unlimited
* hard memlock unlimited
EOF

时间同步:

bash 复制代码
[root@master ~]# yum -y install chrony
[root@master ~]# systemctl restart chronyd
[root@master ~]# chronyc sources -v   # 列出当前配置和使用的所有时间源及其同步状态
[root@master ~]# hwclock -s          # 将硬件时钟的时间同步到系统时钟

启用 IPVS 功能。kube-proxy 的 ipvs 模式依赖以下内核模块:

bash 复制代码
[root@master ~]# cat >> /etc/modules-load.d/ipvs.conf <<EOF
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack_ipv4
ip_tables
ip_set
xt_set
ipt_set
ipt_rpfilter
ipt_REJECT
ipip
overlay
br_netfilter
EOF

[root@master ~]# systemctl restart systemd-modules-load
[root@master ~]# lsmod | grep -e ip_vs -e nf_conntrack_ipv4

这些模块各自的作用:

  • IPVS 核心模块 :ip_vs 是负载均衡基础模块;ip_vs_rr 轮询调度;ip_vs_wrr 加权轮询;ip_vs_sh 源地址哈希。
  • 网络连接与过滤 :nf_conntrack_ipv4 IPv4 连接跟踪(内核 ≥4.19 时合并至 nf_conntrack);ip_tables iptables 基础框架;ipt_REJECT 实现 REJECT 动作。
  • IP 集合管理 :ip_set、xt_set、ipt_set 提供 iptables 与 IP 集合的扩展匹配。
  • 网络隧道与桥接 :ipip IP-over-IP 隧道;overlay Overlay 网络支持;br_netfilter 桥接流量过滤(需配合 net.bridge.bridge-nf-call-iptables=1)。
  • 反向路径过滤 :ipt_rpfilter 反向路径验证,防 IP 欺骗。

典型应用场景包括:Kubernetes 节点初始化(IPVS 模式 kube-proxy 依赖这些模块)、负载均衡服务器、容器网络配置。

2.3 安装 Docker CE 并配置镜像加速

前置环境安装:

bash 复制代码
[root@master ~]# yum install -y yum-utils device-mapper-persistent-data lvm2

其中 yum-utils 提供 yum-config-manager;device mapper 存储驱动需要 device-mapper-persistent-data 和 lvm2。Device Mapper 是 Linux 2.6 内核中支持逻辑卷管理的通用设备映射机制,为存储资源管理的块设备驱动提供了高度模块化的内核架构。

使用阿里云镜像源安装 docker-ce:

bash 复制代码
[root@master ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@master ~]# yum install -y docker-ce

防火墙相关设置(SELinux 已在上文关闭,此处清空 iptables 规则):

bash 复制代码
[root@master ~]# iptables -F

启动 docker 服务:

bash 复制代码
[root@master ~]# systemctl enable --now docker

配置镜像加速与 cgroup 驱动。编辑 /etc/docker/daemon.json,加入 registry-mirrors(镜像加速地址列表,笔记中列举了华为云、DaoCloud、中科大、网易等多个可用镜像,此处保留示例结构)以及关键的一行 "exec-opts": ["native.cgroupdriver=systemd"]:

json 复制代码
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://docker.mirrors.ustc.edu.cn",
    "https://mirror.baidubce.com"
  ],
  "exec-opts": ["native.cgroupdriver=systemd"]
}

注意 JSON 中数组与字段之间的逗号分隔符。把 Docker 的 cgroup 驱动改为 systemd,是为了与后面 kubelet 的 cgroup 驱动保持一致,这是 kubeadm 部署中最常见的坑之一。

重新加载并重启:

bash 复制代码
[root@master ~]# systemctl daemon-reload
[root@master ~]# systemctl restart docker

2.4 安装 cri-dockerd:让 Docker 重新接上 K8s

cri-dockerd 的主要作用是为 Docker Engine 提供一个符合 Kubernetes CRI(Container Runtime Interface)标准的接口,使 Docker 能继续作为 Kubernetes 的容器运行时------尤其是在 Kubernetes 1.24 版本后官方移除对原生 Docker 支持(dockershim)之后。

项目地址在 GitHub(mirantis/cri-dockerd),进入 Releases 页面选择对应版本:

图 2:cri-dockerd 项目主页,从 Release 入口选择与系统匹配的安装包。

图 3:Release 页面按版本提供 rpm / tarball 等制品,选择 el7 x86_64 的 rpm 包。

也可以直接用 wget 下载:

bash 复制代码
[root@master ~]# wget https://github.com/mirantis/cri-dockerd/releases/download/v0.3.4/cri-dockerd-0.3.4-3.e17.x86_64.rpm

下载完成后直接安装:

bash 复制代码
[root@master ~]# rpm -ivh cri-dockerd-0.3.4-3.el7.x86_64.rpm

编辑服务配置文件,在第 10 行中间添加 --pod-infra-container-image 参数,指定 pause 镜像的国内源:

bash 复制代码
[root@master ~]# vim /usr/lib/systemd/system/cri-docker.service
ExecStart=/usr/bin/cri-dockerd \
  --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9 \
  --container-runtime-endpoint fd://

启动服务并检查:

bash 复制代码
[root@master ~]# systemctl daemon-reload
[root@master ~]# systemctl start cri-docker.service
[root@master ~]# systemctl enable cri-docker.service

# 检查 sock 文件是否生成
[root@master ~]# ls /run/cri-*
/run/cri-dockerd.sock

看到 /run/cri-dockerd.sock 就说明 cri-dockerd 已经就绪,后面所有涉及容器运行时的地方都要指向它。

2.5 安装 kubeadm / kubelet / kubectl

配置国内阿里云镜像源:

bash 复制代码
[root@master ~]# cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
       http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

查看可安装版本后,安装 1.28 系列:

bash 复制代码
[root@master ~]# yum list kubeadm.x86_64 --showduplicates | sort -r
[root@master ~]# yum install -y kubeadm-1.28.0-0 kubelet-1.28.0-0 kubectl-1.28.0-0

配置 kubelet:强制指定 kubelet 使用 systemd 作为 cgroup 驱动,确保与 Docker 保持一致;同时将 kube-proxy 的代理模式设置为 ipvs,替代默认的 iptables,提升大规模集群的网络性能。

bash 复制代码
[root@master ~]# vim /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--cgroup-driver=systemd"
KUBE_PROXY_MODE="ipvs"

# 因为还没有初始化产生对应配置文件,先设置开机自启动状态
[root@master ~]# systemctl daemon-reload
[root@master ~]# systemctl enable kubelet.service   # 只是设为开机自启动,并不立即开启

2.6 初始化集群(只在 master 节点操作)

先查看所需镜像清单,再提前拉取,避免初始化时现场下载超时:

bash 复制代码
[root@master ~]# kubeadm config images list --kubernetes-version=v1.28.0 \
  --image-repository=registry.aliyuncs.com/google_containers

registry.aliyuncs.com/google_containers/kube-apiserver:v1.28.0
registry.aliyuncs.com/google_containers/kube-controller-manager:v1.28.0
registry.aliyuncs.com/google_containers/kube-scheduler:v1.28.0
registry.aliyuncs.com/google_containers/kube-proxy:v1.28.0
registry.aliyuncs.com/google_containers/pause:3.9
registry.aliyuncs.com/google_containers/etcd:3.5.9-0
registry.aliyuncs.com/google_containers/coredns:v1.10.1

[root@master ~]# kubeadm config images pull --cri-socket=unix:///var/run/cri-dockerd.sock \
  --kubernetes-version=v1.28.0 --image-repository=registry.aliyuncs.com/google_containers

方式一(推荐):创建初始化配置文件。

bash 复制代码
[root@master ~]# kubeadm config print init-defaults > kubeadm-init.yaml
[root@master ~]# vim kubeadm-init.yaml

修改后的关键内容如下(注释标明了相对默认模板的改动点):

yaml 复制代码
apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
  - system:bootstrappers:kubeadm:default-node-token
  token: abcdef.0123456789abcdef
  ttl: 24h0m0s
  usages:
  - signing
  - authentication
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 192.168.100.128        # master 的 IP 地址
  bindPort: 6443
nodeRegistration:
  criSocket: unix:///var/run/cri-dockerd.sock   # 修改为 cri-dockerd 运行时
  imagePullPolicy: IfNotPresent
  name: master                             # 修改 master 节点主机名
  taints:                                  # 去掉默认的 null
  - effect: NoSchedule                     # 显式添加污点
    key: node-role.kubernetes.io/control-plane
---
apiServer:
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: registry.aliyuncs.com/google_containers   # 修改镜像仓库地址
kind: ClusterConfiguration
kubernetesVersion: 1.28.0
networking:
  dnsDomain: cluster.local
  serviceSubnet: 10.96.0.0/12
  podSubnet: 10.244.0.0/16                 # 增加 Pod 网段
scheduler: {}
---                                          # 末尾追加:kube-proxy 代理模式改为 ipvs
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
---                                          # 更改 kubelet cgroup 驱动为 systemd
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd

执行初始化:

bash 复制代码
[root@master ~]# kubeadm init --config=kubeadm-init.yaml --upload-certs | tee kubeadm-init.log

成功后会输出类似下面的提示,其中包含 worker 节点加入集群所需的 kubeadm join 命令,务必保存:

text 复制代码
Your Kubernetes control-plane has initialized successfully!
...
kubeadm join 192.168.100.128:6443 --token abcdef.0123456789abcdef \
    --discovery-token-ca-cert-hash sha256:48b03ed5f255e142387fa4b58f4e6ef422aea8ec101b2221ea302d50e3921d25

初始化失败处理:重置后重来------

bash 复制代码
# 1. 删除 kubernetes 目录中所有内容
rm -rf /etc/kubernetes/*
# 2. 删除相关进程
pkill -9 kubelet
pkill -9 kube-controll
pkill -9 kube-schedule
# 3. 重置 sock 文件
kubeadm reset -f --cri-socket=unix:///var/run/cri-dockerd.sock

方式二(可选):命令行直接初始化。

bash 复制代码
[root@master ~]# kubeadm init \
  --apiserver-advertise-address=192.168.100.128 \
  --image-repository=registry.aliyuncs.com/google_containers \
  --kubernetes-version=v1.28.0 \
  --cri-socket=unix:///var/run/cri-dockerd.sock \
  --pod-network-cidr=10.244.0.0/16 \
  --service-cidr=10.96.0.0/12 \
  --upload-certs | tee kubeadm-init.log

各参数含义:--apiserver-advertise-address 集群通告地址;--image-repository 指定阿里云镜像仓库地址;--kubernetes-version K8s 版本,与软件包版本一致;--pod-network-cidr Pod 网络范围,需与后面部署的 CNI 组件 yaml 保持一致;--service-cidr 集群内部虚拟网络,Pod 统一访问入口;--upload-certs 将控制节点证书上传到 kubeadm-certs Secret,便于添加多个控制节点;--cri-socket 指定容器运行时。

2.7 配置 kubectl 并检查控制平面

kubectl 是操作 Kubernetes 集群的命令行工具,它在 $HOME/.kube 目录中查找名为 config 的配置文件,也可以通过 KUBECONFIG 环境变量或 --kubeconfig 参数指定其他文件。按初始化成功的提示配置:

bash 复制代码
[root@master ~]# mkdir -p $HOME/.kube
[root@master ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@master ~]# chown $(id -u):$(id -g) $HOME/.kube/config

临时生效方式(退出当前窗口即失效):export KUBECONFIG=/etc/kubernetes/admin.conf。永久生效(推荐):

bash 复制代码
[root@master ~]# echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
[root@master ~]# source ~/.bash_profile

admin.conf 是配置 kubectl 访问集群的客户端配置文件,包含连接集群所需的认证信息、集群信息和上下文信息。检查核心组件健康状态:

bash 复制代码
[root@master ~]# kubectl get cs
NAME                 STATUS    MESSAGE   ERROR
controller-manager   Healthy   ok
etcd-0               Healthy   ok
scheduler            Healthy   ok

2.8 worker 节点加入集群(只在 node 节点操作)

在 node1、node2 上分别执行初始化输出中的 join 命令。注意:必须添加 --cri-socket unix:///var/run/cri-dockerd.sock,否则会报错。

bash 复制代码
[root@node1 ~]# kubeadm join 192.168.100.128:6443 --token abcdef.0123456789abcdef \
    --discovery-token-ca-cert-hash sha256:48b03ed5f255e142387fa4b58f4e6ef422aea8ec101b2221ea302d50e3921d25 \
    --cri-socket unix:///var/run/cri-dockerd.sock

看到 This node has joined the cluster 即表示加入成功,node2 操作相同。

此时查看集群状态,节点会处于 NotReady,coredns 一直 Pending、没有 IP 地址------这是因为还缺网络组件:

text 复制代码
[root@master ~]# kubectl get nodes
NAME     STATUS     ROLES           AGE   VERSION
master   NotReady   control-plane   72m   v1.28.0
node1    NotReady   <none>          23s   v1.28.2
node2    NotReady   <none>          8s    v1.28.2

2.9 部署 Calico 网络插件

Kubernetes 集群的网络比较复杂,并不是集群内部实现的。为了更方便地使用集群,通常使用第三方 CNI 网络插件(Container Network Interface)。CNI 是容器网络接口,作用是实现容器跨主机网络通信;Pod 的 IP 地址段也称为 CIDR。Kubernetes 支持多种网络插件,比如 flannel、calico、canal 等,任选一种即可,本次选择 Calico。

Calico 是一个纯三层的网络解决方案,为容器提供多 node 间的访问通信。Calico 将每一个 node 节点都当作一个路由器(router),每个 Pod 都是虚拟路由器下的终端,各节点通过 BGP(Border Gateway Protocol)边界网关协议学习并在 node 节点生成路由规则,从而将不同 node 节点上的 Pod 连接起来进行通信,是目前 Kubernetes 主流的网络方案。

官方文档与 GitHub 地址:

图 4:Calico 官方文档(docs.tigera.io),按版本提供安装清单与版本兼容说明。

版本对应关系要特别注意:calico.yaml 文件每个版本都有区别,需要满足对应的 K8s 版本。Calico 3.25 版本对应的 K8s 版本为 v1.23---v1.28,与本文的 1.28 正好匹配。

下载并修改 Calico 清单:

bash 复制代码
[root@master ~]# wget --no-check-certificate https://docs.tigera.io/archive/v3.25/manifests/calico.yaml
[root@master ~]# vim calico.yaml
# 找到 4601 行,去掉注释并修改 Pod 网段,与初始化时的 podSubnet 保持一致
- name: CALICO_IPV4POOL_CIDR
  value: "10.244.0.0/16"

部署(需要等待较长时间下载相关组件,主要看网络环境):

bash 复制代码
[root@master ~]# kubectl apply -f calico.yaml

2.10 验证集群

等待全部组件就绪后(笔记中实测等待了约 35 分钟):

text 复制代码
[root@master ~]# kubectl get pods -A
NAMESPACE     NAME                                         READY   STATUS    RESTARTS   AGE
kube-system   calico-kube-controllers-658d97c59c-qctg4     1/1     Running   0          35m
kube-system   calico-node-fgrnf                            1/1     Running   0          35m
kube-system   calico-node-wbb7z                            1/1     Running   0          35m
kube-system   calico-node-x2zkm                            1/1     Running   0          35m
kube-system   coredns-66f779496c-bxpbx                     1/1     Running   0          155m
kube-system   coredns-66f779496c-s4f6n                     1/1     Running   0          155m
kube-system   etcd-master                                  1/1     Running   0          155m
kube-system   kube-apiserver-master                        1/1     Running   0          155m
kube-system   kube-controller-manager-master               1/1     Running   0          155m
kube-system   kube-proxy-bjx8l                             1/1     Running   0          82m
kube-system   kube-proxy-lbsrn                             1/1     Running   0          82m
kube-system   kube-proxy-xm94n                             1/1     Running   0          155m
kube-system   kube-scheduler-master                        1/1     Running   0          155m

排坑:如果出现长时间等待状态,可以在所有节点直接升级内核解决。原因是内核版本过低导致 BPF 文件系统缺失:yum update -y kernel && reboot。

验证 DNS 解析功能(kube-dns 的 ClusterIP 为 10.96.0.10):

bash 复制代码
[root@master ~]# dig -t a www.baidu.com @10.96.0.10
;; ANSWER SECTION:
www.baidu.com.      5   IN  CNAME   www.a.shifen.com.
www.a.shifen.com.   5   IN  A       180.101.49.44

查看节点状态,全部 Ready:

text 复制代码
[root@master ~]# kubectl get nodes
NAME     STATUS   ROLES           AGE    VERSION
master   Ready    control-plane   140m   v1.28.0
node1    Ready    <none>          67m    v1.28.2
node2    Ready    <none>          67m    v1.28.2

[root@master ~]# kubectl get service -n kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   143m

顺带了解两个重要目录:/etc/kubernetes/manifests/ 存放集群各组件的 Pod 配置文件(etcd.yaml、kube-apiserver.yaml、kube-controller-manager.yaml、kube-scheduler.yaml),集群启动时 kubelet 会自动监视该目录并据此创建和管理对应 Pod;CA 证书默认存放在 /etc/kubernetes/pki/ 目录下。

2.11 优化与冒烟测试

配置 kubectl 命令补全:

bash 复制代码
[root@master ~]# yum install bash-completion -y
[root@master ~]# source /usr/share/bash-completion/bash_completion
[root@master ~]# echo "source <(kubectl completion bash)" >> ~/.bashrc
[root@master ~]# source ~/.bashrc

创建一个三副本的 nginx 并以 NodePort 暴露:

bash 复制代码
[root@master ~]# kubectl create deployment nginx --image=nginx --replicas=3
[root@master ~]# kubectl expose deployment nginx --port=80 --target-port=80 --type=NodePort

默认情况下 master 节点存在污点,不接受任何 Pod 资源调度,因此三个 Pod 会分布在 node 节点上:

text 复制代码
[root@master ~]# kubectl get pod,svc
NAME                         READY   STATUS    RESTARTS   AGE
pod/nginx-7854ff8877-gqztv   1/1     Running   0          103s
pod/nginx-7854ff8877-mrzzn   1/1     Running   0          103s
pod/nginx-7854ff8877-sz65x   1/1     Running   0          103s

NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
service/kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP        165m
service/nginx        NodePort    10.98.194.70   <none>        80:32222/TCP   85s

测试访问前先关闭 node 节点防火墙(systemctl stop firewalld.service),然后浏览器访问 nodeIP:32222,node1 和 node2 的地址都可以:

图 5:浏览器访问 http://192.168.100.129:32222/ ,看到 nginx 欢迎页。

图 6:http://192.168.100.136:32222/ 同样可达,说明 Service 的跨节点转发工作正常。

至此,一套 kubeadm + Docker(cri-dockerd)+ Calico 的 K8s 1.28 集群手工部署完成。


三、快速部署:Kuboard 脚本安装 K8s 1.19(整合环境)

进入 DevOps 整合阶段时,笔记换用了一套更轻量的部署方式:通过 Kuboard 官网(https://kuboard.cn/)提供的安装脚本快速搭建 K8s,并选择默认支持 Docker 的版本 1.19------因为这个版本的安装脚本原生按 Docker 运行时编排,与既有的 Jenkins/Harbor 环境衔接最省事。

图 7:Kuboard 官网提供"快速安装 Kubernetes"的入口与教程。

图 8:Kuboard 站内的安装教程页面,本例按教程选择默认支持 Docker 的 1.19 版本。

3.1 前置环境

主机名解析:

bash 复制代码
[root@master ~]# echo "127.0.0.1   $(hostname)" >> /etc/hosts
[root@node1 ~]# echo "127.0.0.1   $(hostname)" >> /etc/hosts

防火墙关闭、禁用 SELinux:

bash 复制代码
[root@master ~]# systemctl disable firewalld.service --now
[root@master ~]# setenforce 0
[root@node1 ~]# systemctl disable firewalld.service --now
[root@node1 ~]# setenforce 0

配置固定 IP。以 master 为例(node1 同理,IP 改为 192.168.100.146):

bash 复制代码
[root@master ~]# vim /etc/sysconfig/network-scripts/ifcfg-ens33
TYPE="Ethernet"
BOOTPROTO="static"
DEFROUTE="yes"
NAME="ens33"
DEVICE="ens33"
ONBOOT="yes"
IPADDR="192.168.100.145"
NETMASK="255.255.255.0"
GATEWAY="192.168.100.2"
DNS1="192.168.100.2"

3.2 安装 Docker 及 kubelet(所有节点)

bash 复制代码
export REGISTRY_MIRROR=https://registry.cn-hangzhou.aliyuncs.com
curl -sSL https://kuboard.cn/install-script/v1.19.x/install_kubelet.sh | sh -s 1.19.5

3.3 初始化 master 节点

在 master 节点执行,只需修改 MASTER_IP 与 APISERVER_NAME 两个变量,其他内容保持脚本原样(需要开启加速,等待时间十分钟左右):

bash 复制代码
# 只在 master 节点执行
export MASTER_IP=192.168.100.145        # 替换为 master 节点实际内网 IP
export APISERVER_NAME=abner.com         # 替换为你想要的 dnsName
# Kubernetes 容器组所在的网段,安装完成后由 kubernetes 创建,事先并不存在于物理网络中
export POD_SUBNET=10.100.0.1/16
echo "${MASTER_IP}    ${APISERVER_NAME}" >> /etc/hosts
curl -sSL https://kuboard.cn/install-script/v1.19.x/init_master.sh | sh -s 1.19.5

注意:export 命令只在当前 shell 会话中有效,开启新的 shell 窗口后如果要继续安装过程,请重新执行这些 export 命令。

初始化完成后终端会给出成功提示:

图 9:init_master.sh 执行完毕后的成功输出。

检查初始化结果,等待 kube-system 下的 Pod 全部 Running:

bash 复制代码
watch kubectl get pod -n kube-system -o wide

图 10:watch 观察 kube-system 命名空间,全部组件进入 Running 状态。

text 复制代码
[root@master ~]# kubectl get nodes -o wide
NAME     STATUS   ROLES    AGE   VERSION   INTERNAL-IP       CONTAINER-RUNTIME
master   Ready    master   14m   v1.19.5   192.168.100.145   docker://19.3.11

3.4 初始化 worker 节点

在 master 上获取 join 命令(生成的 token 有效时间为 2 小时):

bash 复制代码
[root@master ~]# kubeadm token create --print-join-command
kubeadm join abner.com:6443 --token 38699p.691qqnx340kwyhby \
  --discovery-token-ca-cert-hash sha256:8b595b2d12528ae246fac812343f36bc30eff95bab25832b0e109a949c67dee8

在所有 worker 节点执行:

bash 复制代码
# 只在 worker 节点执行
export MASTER_IP=192.168.100.145
export APISERVER_NAME=abner.com
echo "${MASTER_IP}    ${APISERVER_NAME}" >> /etc/hosts
kubeadm join abner.com:6443 --token 38699p.691qqnx340kwyhby \
  --discovery-token-ca-cert-hash sha256:8b595b2d12528ae246fac812343f36bc30eff95bab25832b0e109a949c67dee8

图 11:worker 节点 join 成功的输出信息。

等待约 5 分钟后检查,worker 节点变为 Ready:

text 复制代码
[root@master ~]# kubectl get node
NAME     STATUS   ROLES    AGE     VERSION
master   Ready    master   31m     v1.19.5
node1    Ready    <none>   2m57s   v1.19.5

四、承上启下:单机 Docker 部署阶段的闭环(上期彩蛋)

在引入 K8s 之前,流水线的部署阶段是"Jenkins 通过 deploy.sh 在目标机上停旧容器、删旧镜像、拉新镜像、起新容器"。笔记在这一章开头给出了 deploy.sh 的优化版,重点是自动配置 insecure-registry 并登录 Harbor,使脚本在任意目标机上首次运行时也能自完成仓库配置:

bash 复制代码
#! /bin/bash
harbor_ip=$1
harbor_repo=$2
project=$3
version=$4
container_port=$5
host_port=$6
imageName=$harbor_ip/$harbor_repo/$project:$version
echo $imageName

# 如果当前容器正在运行,则删除
container_id=`docker ps -a | grep ${project} | awk '{print $1}'`
echo $container_id
if [ "$container_id" != "" ]
then
  docker stop $container_id
  docker rm $container_id
fi

# 如果当前镜像已存在,则删除
tag=`docker images | grep ${project} | awk '{print $2}'`
echo $tag
if [[ "$tag" =~ "$version" ]]
then
  docker rmi $imageName
fi

# 非本地登录镜像仓库:自动检测并写入 insecure-registry 配置(此处为优化点)
if grep -q -- "--insecure-registry" /lib/systemd/system/docker.service; then
  echo "已配置过"
else
  echo "初次配置"
  cp /lib/systemd/system/docker.service /lib/systemd/system/docker.service.bak
  sed -i 's\|ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock\|ExecStart=/usr/bin/dockerd -H fd:// --insecure-registry 192.168.18.129 --containerd=/run/containerd/containerd.sock\|g' /lib/systemd/system/docker.service
  systemctl daemon-reload && systemctl restart docker
fi
docker login -u admin -p harbor123 $harbor_ip

# 拉取镜像
docker pull $imageName
# 运行容器
docker run -d -p $host_port:$container_port --name $project $imageName
echo "SUCCESS"

先运行 mytest 作业、再运行 pipeline 作业,单机 Docker 部署形成完美闭环:

图 12:deploy.sh 优化后,mytest 与 pipeline 两个作业先后构建成功,单机部署闭环成立。

但单机部署的局限也很明显:没有副本、没有自愈、没有统一的服务入口。这正是下一章引入 Kubernetes 的动机。


五、Kubernetes 与 DevOps 整合

5.1 准备部署 YAML 并发布到 GitLab

因为 YAML 资源文件可以在 K8s 上创建应用 Pod,所以需要提前创建 YAML,并放到 GitLab 上随流水线一起管理。在 GitLab 项目中新建 pipeline.yml:

图 13:在 GitLab 仓库中在线创建并编辑部署用的 YAML 文件。

YAML 内容包含一个两副本的 Deployment 和一个 NodePort 类型的 Service:

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pipeline
  labels:
    app: pipeline
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pipeline
  template:
    metadata:
      labels:
        app: pipeline
    spec:
      imagePullSecrets:            # 关键凭证:拉取私有 Harbor 镜像
      - name: harbor-secret
      containers:
      - name: pipeline
        image: 192.168.100.145:80/repo/pipeline:v4.0
        imagePullPolicy: Always    # 始终拉取最新版本
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: pipeline
  name: pipeline
spec:
  selector:
    app: pipeline
  ports:
  - port: 8081
    targetPort: 8080
  type: NodePort

推荐操作:在 master 上优先创建 secret 资源,用来保存访问 Harbor 的凭证,否则镜像拉取会失败:

bash 复制代码
kubectl create secret docker-registry harbor-secret \
  --docker-server=192.168.100.145:80 \
  --docker-username=admin \
  --docker-password=harbor123

5.2 对接 Harbor 私有仓库

在所有 K8s 节点(master、node)的 /etc/docker/daemon.json 中添加 Harbor 地址配置:

json 复制代码
{
  "insecure-registries": ["192.168.100.154:80"],
  "registry-mirrors": ["https://registry.cn-hangzhou.aliyuncs.com"],
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": { "max-size": "100m" },
  "storage-driver": "overlay2",
  "storage-opts": [ "overlay2.override_kernel_check=true" ]
}

重启 docker 并测试登录:

bash 复制代码
[root@master ~]# systemctl restart docker
[root@master ~]# docker login 192.168.100.154:80 -u admin -p harbor123
Login Succeeded

5.3 配置 Jenkins 向 K8s master 推送 YAML

在 master 节点准备推送目标目录:

bash 复制代码
[root@master ~]# mkdir /usr/local/k8s
[root@master ~]# chmod 777 /usr/local/k8s/

在 Jenkins 的"系统管理 → 系统配置"中配置 Publish Over SSH 的 SSH Server:Name 填 k8s,Hostname 填 master 地址 192.168.100.145,Username 填 root,Remote Directory 填 /usr/local/k8s,并勾选密码认证填写密码:

图 14:Publish Over SSH 插件的 SSH Server 配置项,红框为需要填写的关键字段。

点击 Test Configuration,显示 Success 后应用并保存:

图 15:Test Configuration 返回 Success,说明 Jenkins 到 K8s master 的 SSH 通道可用。

随后修改 Jenkinsfile 中"Publish Over SSH 通知目标服务器"步骤的内容(在 GitLab 上修改 Jenkinsfile 并提交)。先构建一次,测试 YAML 文件推送:

图 16:阶段日志中 "SSH: Transferred 1 file(s)",阶段视图也出现了"将 yml 文件推送到 k8s 的 master 节点"这一新阶段。

到 master 节点确认文件已到达:

bash 复制代码
[root@master ~]# ls /usr/local/k8s/
pipeline.yml

5.4 Jenkins 免密 SSH 登录 K8s master

推送文件只是第一步,接下来希望流水线能直接执行 ssh root@k8s地址 kubectl apply -f /usr/local/k8s/pipeline.yml 创建资源,因此要实现 SSH 免交互。做法是把 Jenkins 容器中的公钥交给 K8s master:

bash 复制代码
# 进入 Jenkins 容器生成密钥对
[root@jenkins pipline]# docker exec -it jenkins bash
jenkins@0eb81fc79a4a:/$ cd ~
jenkins@0eb81fc79a4a:~$ ssh-keygen -t rsa        # 一路回车
jenkins@0eb81fc79a4a:~$ cd .ssh/
jenkins@0eb81fc79a4a:~/.ssh$ ls
id_rsa  id_rsa.pub
jenkins@0eb81fc79a4a:~/.ssh$ cat id_rsa.pub      # 复制公钥内容

到 master 节点把公钥写入 authorized_keys 并重启 sshd:

bash 复制代码
[root@master ~]# mkdir .ssh
[root@master ~]# cd .ssh/
[root@master .ssh]# vim authorized_keys          # 粘贴 Jenkins 的公钥
[root@master ~]# systemctl restart sshd

回到 Jenkins 容器测试免密执行:

bash 复制代码
jenkins@0eb81fc79a4a:~/.ssh$ ssh root@192.168.100.145 ls /
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

不再询问密码,免密配置完成。

5.5 在流水线中增加"远程执行 kubectl"阶段

在 Jenkins 的流水线语法生成器中拼出 sshPublisher 与 sh 步骤(注意执行的是绝对路径),然后把新阶段写入 GitLab 上的 Jenkinsfile:

groovy 复制代码
stage('远程执行kubectl命令') {
    steps {
        sh 'ssh root@192.168.100.145 kubectl apply -f /usr/local/k8s/pipeline.yml'
    }
}

图 17:GitLab 中编辑 Jenkinsfile,红框为新增的"远程执行 kubectl 命令"阶段;其上是 sshPublisher 推送 YAML 的阶段,其下是 post 中的钉钉通知。

保存配置后还有一个容易漏掉的步骤:在 GitLab 上添加新标签 tag,否则流水线识别不到新添加的 yml 文件(流水线是按 tag 触发拉取的)。进入项目 Tags 页面,点击 New tag:

图 18:在 GitLab 的 New Tag 页面填写 Tag name 为 v4.0,基于 master 创建。

因为本次拉取的项目标签是 4.0,所以 pipeline.yml 中拉取镜像的标签也要同步变更为 v4.0。如果同名标签已存在,把原来的 v4.0 标签删除后重新创建,命名依然写 v4.0。

5.6 触发构建并验证发布结果

立即构建。在"远程执行 kubectl 命令"阶段的日志中可以看到资源创建成功的回显:

text 复制代码
+ ssh root@192.168.100.145 kubectl apply -f /usr/local/k8s/pipeline.yml
deployment.apps/pipeline created
service/pipeline created

图 19:阶段日志回显 deployment 与 service 创建成功,阶段视图完整呈现九个阶段全绿。

在 master 上查看 Pod 与 Service:

text 复制代码
[root@master ~]# kubectl get pod -o wide
NAME                        READY   STATUS    RESTARTS   AGE     IP             NODE
pipeline-7548f9c7f8-cb8hf   1/1     Running   0          6m45s   10.100.166.140 node1
pipeline-7548f9c7f8-fpvfn   1/1     Running   0          6m42s   10.100.166.141 node1

[root@master ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP          11h
pipeline     NodePort    10.96.131.139   <none>        8081:30724/TCP   45m

在 node1 上可以看到 K8s 拉下来的业务镜像和运行中的容器:

text 复制代码
[root@node1 ~]# docker images
REPOSITORY                          TAG    IMAGE ID       CREATED         SIZE
192.168.100.154:80/repo/pipline     v4.0   f9fb39b6f313   7 minutes ago   543MB

打开 node 地址查看网页,http://192.168.100.146:30724/test 返回了新版本页面内容

最后查看钉钉通知,Jenkins 机器人推送了构建结果卡片:

图 21:钉钉群中 Jenkins 机器人通知"成功构建: pipline,版本: v4.0,持续时间: 20 sec and counting"。

至此,"GitLab 提交打 tag → Jenkins 构建/质检/制镜像/推 Harbor → 推送 YAML 并远程 kubectl apply → K8s 集群滚动承载 → 钉钉通知"的完整闭环打通。


六、踩坑清单与经验小结

把两篇笔记中散落的注意事项汇总成一张排坑表:

环节 坑 对策
kubelet 与 Docker cgroup 驱动不一致导致组件起不来 双方统一为 systemd(daemon.json 的 exec-opts 与 kubelet 的 KUBELET_EXTRA_ARGS)
cri-dockerd 服务起不来、Pod 无法创建 服务文件补 --pod-infra-container-image 国内 pause 镜像;确认 /run/cri-dockerd.sock 存在
kubeadm join 不指定运行时直接报错 join 命令追加 --cri-socket unix:///var/run/cri-dockerd.sock
网络插件 calico.yaml 与 K8s 版本不匹配 按版本对应表选择(Calico 3.25 对应 v1.23---v1.28),并把 CALICO_IPV4POOL_CIDR 改成与 podSubnet 一致
Calico 部署卡住 内核过低导致 BPF 文件系统缺失 全节点 yum update -y kernel && reboot
节点 NotReady / coredns Pending 缺少 CNI 网络组件 属于正常中间状态,部署 Calico 后自愈
镜像拉取失败 私有 Harbor 未认证 / 未加信任 先建 harbor-secret;各节点 daemon.json 加 insecure-registries 并 docker login
流水线不触发新版本 改了 Jenkinsfile/YAML 但没打新 tag 在 GitLab 新建 tag(同名需先删旧 tag 再建)
Jenkins 远程执行卡住 SSH 需要交互输密码 Jenkins 容器公钥写入 master 的 authorized_keys 并重启 sshd
sshPublisher 推送无效 相对路径或目录不存在 master 预建 /usr/local/k8s;Jenkinsfile 中 kubectl apply 使用绝对路径
初始化失败 残留配置干扰二次初始化 清理 /etc/kubernetes、pkill 组件进程、kubeadm reset -f 指定 cri-socket 后重来
相关推荐
我不会起名字3221 小时前
K8s Pod 反复重启 CrashLoopBackOff:从 exit code 到根因的 5 步排查
云原生·容器·kubernetes·k8s
瑞克-桑切斯1 小时前
ERP 企业生产管理系统部署手册
运维·docker·云原生·容器·eureka
我不会起名字3222 小时前
K8s 节点磁盘打满导致 Pod 被驱逐:4 类占用与回收顺序
运维·kubernetes·k8s·diskpressure·evicted
孔令飞2 小时前
把 Agent 搬进飞书:飞书 Agent 机器人演示
云原生·go·ai编程
wz97545 小时前
走进云计算:从硬件到云平台,读懂数字化底层基石
云计算
分布式存储与RustFS1 天前
OpenSearch 的索引快照别只留本地:用 repository-s3 存进 RustFS
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
wzq11_6661 天前
Kubernetes集群——存储管理
云原生·容器·kubernetes
孔令飞1 天前
企业智能化升级:智能体平台设计和实现
云原生·go·ai编程
Kari111 天前
企业大模型RAG知识库从0到1搭建指南:腾讯云ADP+JOTO实战
人工智能·云计算·腾讯云