使用k8s k3s kuboard 部署 php hyperf 框架

1.Hyperf 生产环境部署实践:动态更新策略探讨

在基于 Hyperf 框架进行生产环境开发时,如何高效、安全地更新应用代码是一个普遍面临的关键问题。Hyperf 作为构建在 Swoole 之上的高性能框架,其常驻内存的特性意味着:当代码文件发生变更后,必须重启 Worker 进程才能使修改生效,从而加载最新代码到内存中。针对这一核心挑战,业界普遍存在以下几种解决方案:

  1. 服务进程重启 (Brute-force Restart)​

    • 操作方式:​ 直接停止(Kill)现有 Hyperf 服务进程,然后重新启动。
    • 优点:​ 实现极其简单直观,无需额外工具或配置。
    • 缺点:​ 属于"暴力"重启,会导致当前正在处理的请求丢失,服务出现短暂中断,不适用于对可用性要求高的生产环境。
  2. 开发环境热更新机制 (Hyperf Watcher - Development Only)​

    • 操作方式:​ 利用 Hyperf 提供的热更新组件(如 hyperf/watcher),在检测到代码文件变动时自动触发服务重启。
    • 优点:​ 开发体验便捷,显著提升开发效率。
    • 缺点:​ 仅适用于本地开发或测试环境。​ 其本质仍是重启整个 Worker 进程组,在生产环境中同样会造成请求中断。强烈不建议用于生产环境。​
  3. 基于 Docker 与 Nginx Upstream 的蓝绿/滚动发布

    • 核心思路:​ ​ 为更新启动一个包含新代码的新容器实例(新服务)。待新实例完全就绪并通过健康检查后,通过动态修改 Nginx upstream配置,用新容器的实际 IP 地址和端口替换旧容器实例的条目,从而将流量平滑切换到新版本服务。

    • 主要技术挑战:​

      • 新容器实例需动态分配端口(避免与旧实例端口冲突)。
      • 如何自动发现新启动的服务实例(服务发现)。
      • 如何实时、可靠地更新 Nginx upstream配置(配置动态更新)。
    • 优点:​​ 相较于前两种方案,能实现更平滑的流量切换,避免瞬间中断,用户体验更佳。

    • 缺点:​​ 需自行解决端口管理、服务发现和配置动态更新等复杂性问题。

  4. Kubernetes (K8s) 滚动更新 (Rolling Update)​

    • 核心思路(类似方案3,但自动化):​ 在 K8s 集群中,通过 Deployment 等资源定义更新时,会启动包含新代码的 Pod(新服务实例)。当新 Pod 就绪并通过探针检查后,Kubernetes 控制平面会自动将其添加至 Service 的 Endpoints(接收外部流量)。同时,系统将旧 Pod 从 Endpoints 中移除(停止接收新流量)。可配置优雅终止(preStophook)确保旧 Pod 处理完现有请求后被安全删除。
    • 技术挑战:​ 需具备 Kubernetes 基础知识和运维能力。
    • 优点:​ 平台自动化处理滚动更新、健康检查(存活/就绪探针)、自动扩缩容、服务发现、负载均衡及优雅终止,是实现生产环境无缝更新的最佳实践之一。

本文重点聚焦于 Hyperf 应用在生产环境中的部署与更新策略。​​ 文中内容的探讨基于读者已熟悉 Hyperf 框架原理,并对 Kubernetes 核心概念(如 Deployment, Pod, Service, Ingress 等)有基本了解的前提展开。 关于k8s的一些入门教程:

  1. Kubernetes简介
  2. kuboard.cn/learning/
  3. kubernetes.io/zh-cn/docs/...

部署流程图:

1、开发者提交代码到master分支后,执行构建脚本.sh

  1. 拉取master分支最新代码
  2. 生成版本号
  3. 创建临时容器,用于代码的编译
  4. 进入容器执行composer install、生成proxy文件
  5. 将代码打包,减小镜像的大小
  6. 根据版本号创建镜像,将打包好的代码COPY到镜像内
  7. 将镜像推送至 镜像仓库
  8. k8s更新镜像:kubectl set image deployment/<Deployment的名称> <容器名称>=<新镜像地址:新版本标签>

2、k8s接收到命令后,进行镜像的更新

这构建流程不仅适用于php hyperf, golang项目在部署时也同样适用,构建脚本.sh 后期也可替换成Jenkins,流程大同小异,关键点在于 构建脚本.sh 的编写,与k8s的部署与配置

所需的软件和环境

框架mineAdmin3.0,一个基于hyperf框架的企业级后台管理系统。本文中使用该框架做例子。当然也可以是其他项目或框架,比如:gin项目。CICD流程都差不多。

系统 :使用虚拟机进行安装CentOS-7.9,,虚拟机和系统搭建,请看这篇文章

k8sKubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,方便进行声明式配置和自动化。Kubernetes 拥有一个庞大且快速增长的生态系统,其服务、支持和工具的使用范围广泛。

k3sK3s 是一个轻量级的 Kubernetes 发行版,它针对边缘计算、物联网等场景进行了高度优化。K3s 适用于以下场景:边缘计算-Edge、物联网-IoT、CI、Development、ARM、嵌入 K8s

kuboardkuboard 一个Kubernetes 多集群管理界面,可视化的管理k8s/k3s的集群

初始化

准备好2台CentOS-7.9的服务器

名称 ip 配置
k3s-server 192.168.119.131 2核4G
k3s-agent 192.168.119.132 2核4G
csharp 复制代码
#在server节点上执行,需要重启服务器
sudo hostnamectl set-hostname k3s-server

#在agent节点上执行,需要重启服务器
sudo hostnamectl set-hostname k3s-agent

sudo systemctl stop firewalld      # 立即停止防火墙
sudo systemctl disable firewalld  # 禁止开机启动

安装docker

arduino 复制代码
sudo yum remove docker \
    docker-client \
    docker-client-latest \
    docker-common \
    docker-latest \
    docker-latest-logrotate \
    docker-logrotate \
    docker-engine
arduino 复制代码
sudo curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
sudo yum clean all
sudo yum makecache

sudo yum install -y yum-utils device-mapper-persistent-data lvm2

sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

sudo yum makecache fast
bash 复制代码
# 查看可用版本列表
sudo yum list docker-ce --showduplicates | sort -r

# 安装指定版本(26.1.4)
sudo yum install -y docker-ce-26.1.4 docker-ce-cli-26.1.4 containerd.io
bash 复制代码
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://registry.cn-hangzhou.aliyuncs.com",
    "https://docker.mirrors.ustc.edu.cn",
    "https://docker.xuanyuan.run"
  ]
}
EOF
sudo systemctl enable docker
sudo systemctl start docker

#验证docker
[root@localhost ~]# docker -v
Docker version 26.1.4, build 5650f9b

遇到镜像拉取不下来时,建议使用轩辕镜像,方便后续操作。

server 和 agent 服务都需要安装docker

安装k3s

使用离线安装方式进行安装,下载离线包server、agent上都分别下载

bash 复制代码
#下载安装脚本
wget https://rancher-mirror.rancher.cn/k3s/k3s-install.sh
chmod +x k3s-install.sh

# 下载 K3s 二进制和镜像(替换版本号)下载太慢的话,可以在宿主机上下载,再上传到虚拟机
wget -4 https://github.com/k3s-io/k3s/releases/download/v1.33.4-rc1%2Bk3s1/k3s
sudo chmod +x k3s && sudo mv k3s /usr/local/bin/

wget -4 https://github.com/k3s-io/k3s/releases/download/v1.33.4-rc1%2Bk3s1/k3s-airgap-images-amd64.tar
sudo mkdir -p /var/lib/rancher/k3s/agent/images/
sudo cp k3s-airgap-images-amd64.tar /var/lib/rancher/k3s/agent/images/

添加镜像加速: server、agent节点分别添加

bash 复制代码
sudo tee /etc/rancher/k3s/registries.yaml > /dev/null <<'EOF'
mirrors:
  "docker.io":
    endpoint:
      - "https://这里填自己的docker加速地址"
  "k8s.gcr.io":
    endpoint:
      - "https://registry.aliyuncs.com/google_containers"
      - "https://gcr.mirrors.ustc.edu.cn"
EOF

server节点安装

perl 复制代码
INSTALL_K3S_SKIP_DOWNLOAD=true ./k3s-install.sh 

#安装结果:
[root@k3s_server ~]# INSTALL_K3S_SKIP_DOWNLOAD=true ./k3s-install.sh 
[INFO]  Skipping k3s download and verify
[INFO]  Skipping installation of SELinux RPM
[INFO]  Creating /usr/local/bin/kubectl symlink to k3s
[INFO]  Creating /usr/local/bin/crictl symlink to k3s
[INFO]  Skipping /usr/local/bin/ctr symlink to k3s, command exists in PATH at /usr/bin/ctr
[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
[INFO]  Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO]  env: Creating environment file /etc/systemd/system/k3s.service.env
[INFO]  systemd: Creating service file /etc/systemd/system/k3s.service
[INFO]  systemd: Enabling k3s unit
Created symlink from /etc/systemd/system/multi-user.target.wants/k3s.service to /etc/systemd/system/k3s.service.
[INFO]  systemd: Starting k3s

#查看一下状态,是否正确启动了
[root@k3s-server ~]# k3s kubectl get nodes -o wide
NAME         STATUS   ROLES                  AGE   VERSION            INTERNAL-IP       EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION           CONTAINER-RUNTIME
k3s-server   Ready    control-plane,master   11m   v1.33.4-rc1+k3s1   192.168.119.131   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64   containerd://2.0.5-k3s2

接着再server节点查看url和token:

csharp 复制代码
[root@k3s_server ~]# sudo cat /etc/rancher/k3s/k3s.yaml | grep server
    server: https://127.0.0.1:6443
[root@k3s_server ~]# sudo cat /var/lib/rancher/k3s/server/node-token
K104c4194aac85b338608773eda1fea40b817ced6ef6f4b6879b11ab9c9423ee8b3::server:0e1f5e91b653c171e42aef9d8c853bfa

如果安装失败了,进行卸载再重新操作一遍:

bash 复制代码
/usr/local/bin/k3s-uninstall.sh

agent节点安装

K3S_URL:替换成自己的server节点ip地址

K3S_TOEKN:由server获取

ini 复制代码
INSTALL_K3S_SKIP_DOWNLOAD=true K3S_URL=https://192.168.119.131:6443 K3S_TOKEN=K104c4194aac85b338608773eda1fea40b817ced6ef6f4b6879b11ab9c9423ee8b3::server:0e1f5e91b653c171e42aef9d8c853bfa ./k3s-install.sh
perl 复制代码
[root@k3s_agent ~]# INSTALL_K3S_SKIP_DOWNLOAD=true K3S_URL=https://192.168.119.131:6443 K3S_TOKEN=K104c4194aac85b338608773eda1fea40b817ced6ef6f4b6879b11ab9c9423ee8b3::server:0e1f5e91b653c171e42aef9d8c853bfa ./k3s-install.sh
[INFO]  Skipping k3s download and verify
[INFO]  Skipping installation of SELinux RPM
[INFO]  Creating /usr/local/bin/kubectl symlink to k3s
[INFO]  Creating /usr/local/bin/crictl symlink to k3s
[INFO]  Skipping /usr/local/bin/ctr symlink to k3s, command exists in PATH at /usr/bin/ctr
[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
[INFO]  Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh
[INFO]  env: Creating environment file /etc/systemd/system/k3s-agent.service.env
[INFO]  systemd: Creating service file /etc/systemd/system/k3s-agent.service
[INFO]  systemd: Enabling k3s-agent unit
Created symlink from /etc/systemd/system/multi-user.target.wants/k3s-agent.service to /etc/systemd/system/k3s-agent.service.
[INFO]  systemd: Starting k3s-agent
Job for k3s-agent.service failed because the control process exited with error code. See "systemctl status k3s-agent.service" and "journalctl -xe" for details.
perl 复制代码
#查看一下状态,是否正确启动了,再server执行
[root@k3s-server ~]# k3s kubectl get nodes -o wide
NAME         STATUS   ROLES                  AGE   VERSION            INTERNAL-IP       EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION           CONTAINER-RUNTIME
k3s-agent    Ready    <none>                 7s    v1.33.4-rc1+k3s1   192.168.119.132   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64   containerd://2.0.5-k3s2
k3s-server   Ready    control-plane,master   15m   v1.33.4-rc1+k3s1   192.168.119.131   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64   containerd://2.0.5-k3s2

如果安装失败了,进行卸载再重新操作一遍:

bash 复制代码
/usr/local/bin/k3s-agent-uninstall.sh 

安装kuboard

在server节点上执行

ini 复制代码
sudo docker run -d \
  --restart=unless-stopped \
  --name=kuboard \
  -p 8081:80/tcp \
  -p 10081:10081/tcp \
  -e KUBOARD_ENDPOINT="http://{server的ip地址}:80" \
  -e KUBOARD_AGENT_SERVER_TCP_PORT="10081" \
  -v /root/kuboard-data:/data \
  eipwork/kuboard:v3

进入管理后台: http://192.168.119.131:8081/ admin Kuboard123

集群导入成功,到此 kuboard 的安装就完成了

安装mineadmin

关于mineAdmin的部署与启动这里就不再进行说明。要确保服务能正常启动.

csharp 复制代码
docker run -it \
  --name mineadmin \
  -p 9501:9501 \
  -v $(pwd):/opt/www \
  mineadmin:latest

#先在.env文件配置好数据库和redis新增
#进入容器
dokcer exec -it mineadmin /bin/bash

# 安装依赖包(开发环境)
composer install -vvv

# 创建数据库(可选,也可以手动创建)
mysql -u root -p -e "CREATE DATABASE mineadmin CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

# 执行数据库迁移
php bin/hyperf.php migrate

# 填充初始数据
php bin/hyperf.php db:seed
#启动程序
/opt/www # php bin/hyperf.php start
[INFO] Worker#1 started.
[INFO] Worker#0 started.
[INFO] Worker#2 started.
[INFO] Worker#3 started.
[INFO] HTTP Server listening at 0.0.0.0:9503
[INFO] HTTP Server listening at 0.0.0.0:9501
[INFO] Worker#6 started.
[INFO] Worker#4 started.
[INFO] Worker#7 started.
[INFO] Worker#5 started.

创建镜像仓库

镜像仓库可以自己搭建、者使用阿里云的镜像仓库,这里以阿里云镜像仓库为例。

在server、agent节点上进行登入

ini 复制代码
docker login --username=xxxx registry.cn-shenzhen.aliyuncs.com

到此,阿里云镜像仓库创建完毕,基础镜像和项目镜像都会上传到这个仓库里。

构建镜像

为了加快镜像的构建速度,将镜像分为:基础镜像项目镜像,基础镜像是php、swoole等一些配置。项目镜像包含了项目的代码。

基础镜像

./deploy/base.dockerfile

基础镜像只需要构建一次。后面就不需要了,除非修改了php配置或者增加了php扩展等

bash 复制代码
FROM hyperf/hyperf:8.1-alpine-v3.19-swoole-slim
LABEL maintainer="MineManage Developers <group@stye.cn>" version="1.0" license="MIT" app.name="MineManage"

##
# ---------- env settings ----------·
##
# --build-arg timezone=Asia/Shanghai
ARG timezone

ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \
    APP_ENV=prod \
    SCAN_CACHEABLE=(true)

# update
RUN set -ex \
    # show php version and extensions
    && php -v \
    && php -m \
    && php --ri swoole \
    #  ---------- some config ----------
    && cd /etc/php* \
    # - config PHP
    && { \
        echo "upload_max_filesize=128M"; \
        echo "post_max_size=128M"; \
        echo "memory_limit=1G"; \
        echo "date.timezone=${TIMEZONE}"; \
    } | tee conf.d/99_overrides.ini \
    # - config timezone
    && ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
    && echo "${TIMEZONE}" > /etc/timezone \
    # ---------- clear works ----------
    && rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
    && echo -e "\033[42;37m Build Completed :).\033[0m\n"

# update
RUN set -ex \
    #  ---------- some config ----------
    && cd /etc/php81 \
    # - config timezone
    && ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
    && echo "${TIMEZONE}" > /etc/timezone \
    && echo -e "\033[42;37m Build Completed :).\033[0m\n"

RUN set -ex && \
    sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
    && apk update \
    && apk add --no-cache libstdc++ openssl git bash autoconf pcre2-dev zlib-dev re2c gcc g++ make \
    php81-pear php81-dev php81-tokenizer php81-fileinfo php81-simplexml php81-xmlwriter \
    && apk add --no-cache --virtual .build-deps $PHPIZE_DEPS zlib-dev libaio-dev openssl-dev curl-dev  c-ares-dev \
    && pecl81 channel-update pecl.php.net \
    && pecl81 install --configureoptions 'enable-reader="yes"' xlswriter \
    && echo "extension=xlswriter.so" >> /etc/php81/conf.d/60-xlswriter.ini \
    && php -m \
    && php -v \
    && php --ri swoole \
    && mkdir -p /app-src \
    # ---------- clear works ----------
    && apk del .build-deps \
    && rm -rf /var/cache/apk/* /tmp/* /usr/share/man /usr/local/bin/php* \
    && echo -e "\033[42;37m Build Completed :).\033[0m\n"

ENV LD_PRELOAD /usr/lib/preloadable_libiconv.so

WORKDIR /opt/www
csharp 复制代码
# 项目根目录执行
docker build -f ./deploy/base.dockerfile -t mineadmin:base  .

[root@k3s-server deploy]# docker images | grep mineadmin
mineadmin                                   base                           905322f09690   About a minute ago   383MB
mineadmin                                   latest                         331df88ac067   19 hours ago         118MB
#打标签
docker tag 905322f09690 registry.cn-shenzhen.aliyuncs.com/byd_dev/mineadmin:base
#推送镜像
docker push registry.cn-shenzhen.aliyuncs.com/byd_dev/mineadmin:base

如果遇到镜像拉取不下来,请更换其他源。或者提前把 hyperf/hyperf:8.1-alpine-v3.19-swoole-slim pull 下来

项目镜像

./deploy.dockerfile.tlp

bash 复制代码
# 这里要替换成自己的基础镜像地址

FROM registry.cn-shenzhen.aliyuncs.com/byd_dev/mineadmin:base

COPY {package_name} /opt/www/{project_tar_gz} 
RUN rm -rf /opt/www/{package_name}

EXPOSE 9501 9502 9503

修改.dockerignore 文件

diff 复制代码
**
!app/
!bin/
!config/
!databases/
!plugin/
!storage/
!composer.*
!.env.example
!*.tar.gz

编写build.sh

./deploy/build.sh

bash 复制代码
#!/bin/bash

# --- Color Definitions ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# --- Script Configuration ---

#镜像仓库地址
REGISTRY_URL="registry.cn-shenzhen.aliyuncs.com/byd_dev/mineadmin"
#构建容器名称
CONTAINER_NAME="mineadmin"
#打包名称(项目名称)
PROJECT_NAME="mineadmin"
#k8s命名空间
K8S_NAMESPACE="mineadmin"
PROJECT_NAME_TAR_GZ="${PROJECT_NAME}.tar.gz"
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
WEB_PATH=$(dirname "$SCRIPT_DIR")

# --- Main Script ---
cd "$WEB_PATH" || exit

# 1. Parse branch argument
BRANCH=${1:-master}
if [ "$BRANCH" != "master" ] && [ "$BRANCH" != "dev" ]; then
  echo -e "${RED}❌ 错误:无效的分支名称。请使用 'master' 或 'dev'。${NC}"
  exit 1
fi

echo -e "${CYAN}===================================================${NC}"
echo -e "${CYAN}🚀 开始为分支 '${YELLOW}$BRANCH${CYAN}' 执行构建...${NC}"
echo -e "${CYAN}===================================================${NC}"

# 2. Pull latest code from Git
echo -e "\n${BLUE}Step 1: 从 Git 拉取最新代码...${NC}"
git checkout $BRANCH > /dev/null 2>&1
git pull origin $BRANCH > /dev/null 2>&1
if [ $? -ne 0 ]; then
  echo -e "${RED}❌ 错误:从 Git 拉取代码失败。${NC}"
  exit 1
fi
echo -e "${GREEN}✔️ Git 代码拉取成功。${NC}"

# Step 2: Get version from timestamp1
echo -e "\n${BLUE}Step 2: 正在生成时间戳版本号...${NC}"
VERSION=$(date +%y%m%d%H%M%S)
if [ -z "$VERSION" ]; then
  echo -e "${RED}❌ 错误:生成时间戳版本号失败。${NC}"
  exit 1
fi
echo -e "${GREEN}✔️ 使用时间戳作为版本号:${YELLOW}$VERSION${NC}"


# Step 3: Prepare configuration file
# 如果使用了k8s的configMap功能,就不需要copy配置文件了
echo -e "\n${BLUE}Step 3: 准备配置文件...${NC}"
ENV_FILE=".env.$BRANCH"
if [ ! -f "$ENV_FILE" ]; then
  echo -e "${RED}❌ 错误:配置文件 '$ENV_FILE' 不存在。${NC}"
  exit 1
fi
cp "$ENV_FILE" .env
echo -e "${GREEN}✔️ 已将 '$ENV_FILE' 复制到 '.env'。${NC}"

# Check container status
echo -e "\n${BLUE}Step 4: 检查容器状态...${NC}"
CONTAINER_ID=$(docker ps -qf "name=^${CONTAINER_NAME}$")
if [ -z "$CONTAINER_ID" ]; then
  echo -e "${RED}❌ 错误:找不到名为 '$CONTAINER_NAME' 的正在运行的容器。${NC}"
  echo -e "${YELLOW}请确保容器已根据环境要求提前启动。${NC}"
  exit 1
fi
echo -e "${GREEN}✔️ 容器 '$CONTAINER_NAME' 正在运行。${NC}"

# Execute commands inside the container11
echo -e "\n${BLUE}Step 5: 在容器内执行构建命令...${NC}"

echo -e "  - 正在执行 'composer install'..."
docker exec $CONTAINER_NAME composer install --no-dev --optimize-autoloader > /dev/null 2>&1
if [ $? -ne 0 ]; then
  echo -e "${RED}  - ❌ 错误:'composer install' 执行失败${NC}"
  exit 1
fi

echo -e "  - 正在执行 'composer dump-autoload -o'..."
docker exec $CONTAINER_NAME composer dump-autoload -o > /dev/null 2>&1
if [ $? -ne 0 ]; then
  echo -e "${RED}  - ❌ 错误:'composer dump-autoload -o' 执行失败。${NC}"
  exit 1
fi

echo -e "  - 正在生成 proxy 代理文件..."
docker exec $CONTAINER_NAME php /opt/www/bin/hyperf.php > /dev/null 2>&1
if [ $? -ne 0 ]; then
  echo -e "${RED}  - ❌ 错误:生成代理文件命令执行失败。${NC}"
  exit 1
fi

docker exec $CONTAINER_NAME test -d /opt/www/runtime/container
if [ $? -ne 0 ]; then
  echo -e "${RED}  - ❌ 错误:Hyperf 代理文件目录 /opt/www/runtime/container 未找到,生成失败。${NC}"
  exit 1
fi
echo -e "${GREEN}✔️ 容器内构建命令执行成功。${NC}"

# 5. Package the code1
PACKAGE_NAME="${PROJECT_NAME}-${VERSION}.tar.gz"
echo -e "\n${BLUE}Step 6: 📦 将代码打包到 '${YELLOW}$PACKAGE_NAME${BLUE}'...${NC}"
tar --exclude=.dockerignore \
    --exclude='*.log' \
    --exclude='.idea' \
    --exclude='.devcontainer' \
    --exclude='.git' \
    --exclude='runtime' \
    --exclude='*.tar.gz' \
    -zcvf $PACKAGE_NAME ./* > /dev/null 2>&1
if [ $? -ne 0 ]; then
  echo -e "${RED}❌ 错误:打包代码失败。${NC}"
  exit 1
fi
echo -e "${GREEN}✔️ 代码打包成功。${NC}"

# 6. Build Docker image
IMAGE_TAG="$VERSION"
FULL_IMAGE_NAME="${REGISTRY_URL}:${IMAGE_TAG}"
TEMPLATE_FILE="deploy/deploy.dockerfile.tlp"
DEPLOY_DOCKERFILE="deploy/deploy.dockerfile"

echo -e "\n${BLUE}Step 7: 🐳 构建 Docker 镜像...${NC}"
echo -e "  - 镜像名称: ${YELLOW}$FULL_IMAGE_NAME${NC}"

if [ ! -f "$TEMPLATE_FILE" ]; then
  echo -e "${RED}❌ 错误:模板文件 '$TEMPLATE_FILE' 不存在。${NC}"
  exit 1
fi

sed -e "s|{package_name}|${PACKAGE_NAME}|g" -e "s|{project_tar_gz}|${PROJECT_NAME_TAR_GZ}|g" "$TEMPLATE_FILE" > "$DEPLOY_DOCKERFILE"
if [ $? -ne 0 ]; then
  echo -e "${RED}❌ 错误:创建 '$DEPLOY_DOCKERFILE' 失败。${NC}"
  exit 1
fi

docker build -f "$DEPLOY_DOCKERFILE" -t "$FULL_IMAGE_NAME" . > /dev/null 2>&1
if [ $? -ne 0 ]; then
  echo -e "${RED}❌ 错误:构建 Docker 镜像失败。${NC}"
  rm "$DEPLOY_DOCKERFILE"
  exit 1
fi
echo -e "${GREEN}✔️ Docker 镜像构建成功。${NC}"

# Clean up temporary Dockerfile
rm "$DEPLOY_DOCKERFILE"

# 7. Push image to registry
echo -e "\n${BLUE}Step 8: 📤 上传镜像到仓库...${NC}"
docker push $FULL_IMAGE_NAME > /dev/null 2>&1
if [ $? -ne 0 ]; then
  echo -e "${RED}❌ 错误:上传镜像失败。${NC}"
  exit 1
fi
echo -e "${GREEN}✔️ 镜像上传成功。${NC}"

echo -e "\n${GREEN}===================================================${NC}"
echo -e "${GREEN}✨ 构建和上传成功完成! ✨${NC}"
echo -e "${GREEN}===================================================${NC}"
echo -e "镜像名称:${YELLOW}$FULL_IMAGE_NAME${NC}\n"

# 8. 更新k8s
echo -e "\n${BLUE}Step 9: 🚀 更新 Kubernetes Deployment...${NC}"
# 设置mineadmin 进行镜像更新
kubectl set image deployment/${PROJECT_NAME} ${PROJECT_NAME}=${FULL_IMAGE_NAME}
if [ $? -ne 0 ]; then
  echo -e "${RED}❌ 错误:触发 Kubernetes Deployment 更新失败。${NC}"
  echo -e "${RED}请检查:${NC}"
  echo -e "${RED}  1. 是否有权限访问 Kubernetes 集群 (kubectl config)。${NC}"
  echo -e "${RED}  2. Deployment 名称 '${PROJECT_NAME}' 和容器名称 '${PROJECT_NAME}' 是否正确。${NC}"
  exit 1
fi
echo -e "${GREEN}✔️ Deployment 更新已成功触发。${NC}"

# 9. (可选但强烈推荐) 检查更新状态
echo -e "\n${BLUE}Step 10: 🔍 监控滚动更新状态...${NC}"
kubectl rollout status deployment/${PROJECT_NAME}
if [ $? -ne 0 ]; then
  echo -e "${RED}❌ 错误:Deployment 滚动更新失败。请使用 'kubectl describe deployment/${PROJECT_NAME}' 查看详情。${NC}"
  exit 1
fi

echo -e "\n${GREEN}===================================================${NC}"
echo -e "${GREEN}✅ CI/CD 流程全部成功完成!应用已更新。 ✨${NC}"
echo -e "${GREEN}===================================================${NC}"

连接k3s集群

在执行build.sh脚本之前,需要对k8s集群有访问权限。如果是在k3s-server 节点上执行构建脚本的话,就不需要做此操作。在其他服务器上构建时,需要做以下操作:

bash 复制代码
# 下载最新版本的 kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"

# 赋予执行权限
chmod +x ./kubectl

# 将其移动到您的PATH路径下,例如 /usr/local/bin
sudo mv ./kubectl /usr/local/bin/kubectl

# 创建 .kube 目录 : kubectl 默认会在用户的主目录下的 .kube 文件夹中寻找名为 config 的文件。
mkdir -p $HOME/.kube

#将 k8s-service节点上的配置文件复制过来:sudo cat /etc/rancher/k3s/k3s.yaml,修改server:https://192.168.119.131:6443

vim $HOME/.kube/config
csharp 复制代码
# 在构建节点上执行,看是否能获取到集群信息
[root@localhost ~]# kubectl get nodes
NAME         STATUS   ROLES                  AGE     VERSION
k3s-agent    Ready    <none>                 5d19h   v1.33.4-rc1+k3s1
k3s-server   Ready    control-plane,master   5d19h   v1.33.4-rc1+k3s1

执行build.sh

在项目根目录执行 sh deploy/build.sh 看是否能够正常:

  1. 打包出来的tag.gz文件,是否正常
  2. 是否能push到镜像仓库
  3. 对刚创建出来的 镜像,进行测试。根据镜像 创建一个临时容器,进入容器,启动项目,看是否能启动成功

到此完成了项目的拉取、编译、构建镜像功能。下一步就是在k8s(k3s)上使用镜像部署项目

k3s部署项目

创建命名空间

配置ConfigMap

ConfigMap 是 Kubernetes 中用于存储非敏感配置数据的资源对象。它可以将配置信息(如环境变量、配置文件、命令行参数等)与应用程序代码分离,从而实现配置的集中管理和动态更新。

也可通过yaml文件创建:deploy/k8s/ConfigMap.yaml

configMap会以环境变量的方式,写入pod的容器内,在代码里还是使用env('APP_NAME', 'MineAdmin'), 方式获取配置文件信息

创建Deployment

创建镜像仓库账号密码

配置启动命令

bash 复制代码
/bin/sh -c 'tar -zxf mineadmin.tar.gz . && php bin/hyperf.php start'

配置服务端口

项目启动了哪些服务就填写对应的端口

配置探针

启动探针,用于判断容器是否启动成功。

存活探针 就绪探针 每个探针都有自己所属的功能和应用场景,根据实际情况来选择。一般配置存活探针、就绪探针就可以保证服务的正常运行

配置文件存储

将容器内的logs目录挂载到宿主机的目录下,方便后续的日志采集或者查看日志

创建service

启动deployment 启动成功:

配置Ingress

由于我们是在本地环境下部署的,所有得在本机hosts上添加域名。 C:\Windows\System32\drivers\etc\hosts

访问admin-mineadmin.com

在生成环境中,前面还有一层高可用的 LBS做负载均衡。流量路径:用户请求->DNS->lbs->ingress->service->pod->服务

遇到的问题

在创建deployment过程中,会遇到各种各样的问题,以下是一些解决思路:

1、首先保证项目镜像是正常,能正常启动的。可以在agent节点,把镜像拉取下来,创建一个临时容器,看项目是否能够正确启动。

bash 复制代码
docker -it  --namne test1 registry.cn-shenzhen.aliyuncs.com/byd_dev/mineadmin:250820150754
#创建临时容器
docker run -it  --namne test1 registry.cn-shenzhen.aliyuncs.com/byd_dev/mineadmin:250820150754
#解压启动
tar -zxf mineadmin.tar.gz . && php bin/hyperf.php start

项目的镜像正常了,到了k8s不正常,怎么排查?

初始化容器时,先不启动服务,先执行sleep 3600。 这样子容器就启动成功了,然后可以手动的进入容器,检查是什么原因导致服务启动失败的

bash 复制代码
#k8s使用的是containerd,命令跟docker一致,只不过docker 换成了 crictl
#有可能 mineadmin-f6dcd85c9 部署在server 或 agent 两边都查一下
crictl ps -a | grep mineadmin-f6dcd85c9
#进入容器
crictl exec -it {CONTAINER} /bin/bash

进入容器之后,首先查看

  1. 代码是否打包成功、解压出来的文件是否有缺失
  2. configMap 是否写入容器的环境变量里。执行 env 查看容器环境变量
  3. 然后尝试启动服务看服务是否正常启动。php bin/hyperf.php start

启动成功之后,退出容器,回到管理后台,更新回正常的启动命令,然后更新Deployment

拉取镜像失败

分别在 server agent 节点 创建 /etc/rancher/k3s/registries.yaml 文件

bash 复制代码
sudo tee /etc/rancher/k3s/registries.yaml > /dev/null <<'EOF'
mirrors:
  "docker.io":
    endpoint:
      - "https://这里填自己的docker加速地址"
  "k8s.gcr.io":
    endpoint:
      - "https://registry.aliyuncs.com/google_containers"
      - "https://gcr.mirrors.ustc.edu.cn"
EOF
arduino 复制代码
#agent节点 重启k3s-agent
sudo systemctl restart k3s-agent

#server节点 重启k3s
sudo systemctl restart k3s

总结

到此完成了代码镜像的构建、k8s的安装与部署,以及代码的发布。该方案还有许多完善的地方,比如:

  1. 构建脚本.sh 可以更换成 Jenkins ,实现CICD自动发布
  2. 多个节点下服务日志的收集。需要使用ELK进行收集与查询

生成环境下,建议使用各个云产商的k8s服务,毕竟开发人员不是专业的运维人员,k8s的运维还是比较复杂的。比如阿里云的ACK功能,后面有时间的话,会把Jenkins加上。

相关推荐
长城20248 小时前
从词源和输出生成等角度详细解析PHP中常用文件操作类函数
php·文件·函数·文件操作函数
长城20248 小时前
PHP如何使用JpGraph生成3D饼形图?
开发语言·php·jpgraph·3d饼形图
熬夜苦读学习15 小时前
Reactor 反应堆模式
运维·服务器·网络·网络协议·http·智能路由器·php
小森林815 小时前
分享一次Guzzlehttp上传批量图片优化的经历
后端·php
THMAIL16 小时前
大模型0基础开发入门与实践:第11章 进阶:LangChain与外部工具调用
开发语言·langchain·php
分享点2 天前
Laravel 使用阿里云OSS S3 协议文件上传
阿里云·php·laravel
苏琢玉2 天前
订单号老是撞车?我写了个通用 PHP ID 生成器
php·composer
BingoGo2 天前
PHP 测试框架 Pest v4 正式发布 革命性的浏览器测试体验
后端·php
搬码临时工2 天前
通过自定义域名访问内网的web服务和tcp应用:内网ip到局域网外域名访问过程
服务器·tcp/ip·php