zoukankan      html  css  js  c++  java
  • Kubernetes 部署 Laravel 应用的最佳实践

    实验前提

    • 需要你有 macOS 开发环境,本文以此为例,其他类型的开发环境请自行搭建。
    • 需要你对 YAML 这一专门用来写配置文件的语言有所了解。
    • 需要你对 Docker 有一些基本的了解。
    • 需要你对 Kubernetes 中的 Node、Pod、ReplicaSet、Deployment、Service、Ingress、ConfigMap 等一些核心基础概念有一定的了解。
    • 本文实验以学习为主,仅适合本地开发环境,请勿使用在生产环境中。

    YAML 配置文件下载地址:

    git clone https://github.com/jxlwqq/kubernetes-examples.git
    cd deploying-laravel-7-with-mysql-and-redis
    

    安装 Docker for Mac

    下载地址:https://hub.docker.com/editions/community/docker-ce-desktop-mac

    启动并开启 Kubernetes 功能,功能开启过程中,Docker 将会自动拉取 Kubernetes 相关镜像,所以全程需要科-学-上-网。

    为啥不使用 minikube?minikube + virtualbox + kubectl 安装起来太繁琐了,而且即使科-学-上-网了你也不一定能搞定。当然阿里云提供了一篇安装教程可以参考。

    本地端口准备

    请确保本地 localhost 的 80 端口没有被占用,已在使用的请在实验期间暂时关闭占用 80 端口的服务。

    切换集群

    如果你本地有多个 Kubernetes 的集群配置,请先切换至名为 docker-desktop 的集群:

    kubectl config use-context docker-desktop
    

    创建 MySQL 服务

    为了提高 Pod 的启动速度,我们首先准备好 MySQL 的镜像:

    docker pull mysql:5.7
    

    部署 MySQL 服务:

    kubectl apply -f mysql-deployment-and-service.yaml
    

    注意:deployment 在生产场景中对 MySQL 这种有状态的服务并不适合。

    yaml 文件解读:

    kind: Deployment # 对象类型
    apiVersion: apps/v1 # api 版本
    metadata: # 对象元数据
      name: mysql-deployment # 对象名称,加后缀 -deployment 主要是为了新手看的明白,也可以直接改为 name: mysql
      labels: # 对象标签
        app: mysql # 标签
    spec: # 对象规约,注意这里的对象指的是 Deployment 对象
      selector: # 选择器,不太恰当的类比就相当于 CSS 选择器
        matchLabels: # 匹配标签
          app: mysql # Pod 标签
      strategy: # 部署策略
        type: Recreate # Recreate 重新创建  RollingUpdate 滚动升级
      template: # 模版
        metadata: # 元数据
          labels: # 标签
            app: mysql # Pod 的标签,这里的值与上面的 selector matchLabels 的标签对应
        spec: # 对象规约,注意这里的对象指的是 Pod 对象
          containers: # 容器
            - name: mysql # 容器名称
              image: mysql:5.7 # 容器镜像
              env: # 环境变量
                - name: MYSQL_ALLOW_EMPTY_PASSWORD
                  value: 'true'
              ports: # 端口
                - containerPort: 3306 # 容器端口
              volumeMounts: 
                - mountPath: /var/lib/mysql
                  name: mysql-storage
          volumes:
            - name: mysql-storage
              persistentVolumeClaim: # PersistentVolume(PV)是一块集群里由管理员手动提供,或 kubernetes 通过 StorageClass 动态创建的存储。 PersistentVolumeClaim(PVC)是一个满足对 PV 存储需要的请求。
                claimName: mysql-persistentvolumeclaim
    ---
    kind: PersistentVolumeClaim # 对象类型
    apiVersion: v1 # api 版本
    metadata: # 元数据
      name: mysql-persistentvolumeclaim # 对象名称
      labels: # 标签
        app: mysql
    spec: # 对象规约
      accessModes: # 访问方式
        - ReadWriteOnce
      resources: # 资源
        requests: # 请求配置
          storage: 1Gi # 大小,这里主要做了实验,我们就设置小点的
    ---
    kind: Service # 对象类型
    apiVersion: v1 # api 版本
    metadata: # 元数据
      name: mysql-service # 对象名称
      labels: # 标签
        app: mysql
    spec: # 对象规约
      selector: # 选择器
        app: mysql # Pod 的标签
      ports: # 端口
        - port: 3306 # 端口号
          targetPort: 3306 # 与 Pod  containerPort 端口号一致
    

    进入 Pod:

    kubectl get pods # 获取 pods 列表
    kubectl exec -it mysql-deployment-79cdbc594-rmhjk mysql # pod 名称改成你自己
    

    创建一个名为 laravel 的数据库:

    create database laravel;
    

    数据库 laravel 创建完成后,即可 exit。

    创建 Redis 服务

    为了提高 Pod 的启动速度,我们首先准备好 Redis 的镜像:

    docker pull redis
    

    部署 Redis 服务:

    kubectl apply -f redis-deployment-and-service.yaml
    

    yaml 文件解读:

    kind: Deployment # 对象类型
    apiVersion: apps/v1 # api 版本
    metadata: # 元数据
      name: redis-deployment # 对象名称
      labels: # 对象标签
        app: redis
    spec: # 对象规约,这里的对象指的是 Deployment 对象
      selector: #  选择器
        matchLabels: # 匹配标签
          app: redis 
      template: # 模版
        metadata: # 元数据
          labels: # 标签
            app: redis
        spec: # 对象规约,这里的对象指的是 Pod 对象
          containers: # 容器 
            - name: redis # 容器名称
              image: redis # 容器镜像
              ports: # 端口
                - containerPort: 6379 # Redis 的服务端口
    ---
    kind: Service # 对象类型
    apiVersion: v1 # api 版本
    metadata: # 元数据
      name: redis-service # 对象名称
    spec: # 对象规约
      selector: # 选择器
        app: redis # 标签,选择 标签包含 app: redis 的 一组 Pod
      ports: # 端口
        - port: 6379 # 暴露的端口,如果 port 和 targetPort 一致,targetPort 可以不写
    

    创建 Laravel 服务

    为了提高 Pod 的启动速度,我们首先准备好 Laravel-demo 的镜像:

    docker pull jxlwqq/laravel-7-kubernetes-demo
    

    镜像地址:https://hub.docker.com/repository/docker/jxlwqq/laravel-7-kubernetes-demo

    源码地址:https://github.com/jxlwqq/laravel-7-kubernetes-demo

    源码基于官方 Laravel v7 版本,做了以下修改:点击查看compare

    • 增加了 Docker 镜像构建相关的文件:Dockerfile 和 .dockerignore
    • 增加了一个 config/apache2/sites-available/laravel.conf
    • 增加了一个 crontab 文件,执行 php artisan schedule:run
    • 增加了一个 docker/entrypoint.sh 文件:Docker 容器启动时执行的命令

    我这里把核心的 Dockerfile 和 entrypoint.sh 的代码贴过来:

    Dockerfile:

    # First Stage
    FROM node:alpine as frontend
    COPY package.json package-lock.json /app/
    RUN cd /app 
        && npm install
    COPY webpack.mix.js /app/
    COPY resources/js/ /app/resources/js/
    COPY resources/sass/ /app/resources/sass/
    RUN cd /app 
          && npm run production
    
    # Second Stage
    FROM composer as composer
    COPY database/ /app/database/
    COPY composer.json composer.lock /app/
    RUN cd /app 
          && composer install 
               --optimize-autoloader 
               --ignore-platform-reqs 
               --prefer-dist 
               --no-interaction 
               --no-plugins 
               --no-scripts 
               --no-dev
    
    # Third Stage
    FROM php:7.4-apache-buster
    RUN apt-get update 
        && apt-get install -y vim cron libmagickwand-dev imagemagick
    RUN docker-php-ext-install intl pdo_mysql bcmath 
        && pecl install redis imagick 
        && docker-php-ext-enable opcache redis imagick 
        && cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
    RUN apt-get clean 
        && apt-get autoclean 
        && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
    
    ARG LARAVEL_PATH=/var/www/laravel
    WORKDIR ${LARAVEL_PATH}
    
    COPY . ${LARAVEL_PATH}
    COPY --from=composer /app/vendor/ ${LARAVEL_PATH}/vendor/
    COPY --from=frontend /app/public/js/ ${LARAVEL_PATH}/public/js/
    COPY --from=frontend /app/public/css/ ${LARAVEL_PATH}/public/css/
    COPY --from=frontend /app/mix-manifest.json ${LARAVEL_PATH}/mix-manifest.json
    
    RUN cd ${LARAVEL_PATH} 
          && php artisan package:discover 
          && chown www-data:www-data bootstrap/cache 
          && chown -R www-data:www-data storage/
    
    RUN rm /etc/apache2/sites-enabled/*
    COPY config/apache2 /etc/apache2/
    RUN a2enmod rewrite headers 
        && a2ensite laravel
    
    COPY docker/entrypoint.sh /usr/local/bin/entrypoint
    RUN chmod +x /usr/local/bin/entrypoint
    ENTRYPOINT ["/usr/local/bin/entrypoint"]
    

    entrypoint.sh:

    #!/usr/bin/env bash
    
    set -e
    
    cd /var/www/laravel
    rm -f public/storage
    
    echo 'migrate'
    php artisan migrate --force
    
    echo 'publish'
    # php artisan vendor:publish --tag=laravel-pagination
    
    echo 'cache'
    php artisan config:cache
    php artisan view:cache
    php artisan route:cache
    php artisan event:cache
    
    echo "cron"
    mkdir -p /var/spool/cron/crontabs/
    cp crontab /var/spool/cron/crontabs/root
    chmod 0644 /var/spool/cron/crontabs/root
    crontab /var/spool/cron/crontabs/root
    cron -f &
    
    echo "queue"
    php artisan queue:work --queue={default} --verbose --tries=3 --timeout=90 &
    
    echo 'http'
    exec apache2-foreground
    

    部署 Laravel 服务:

    kubectl apply -f configmap.yaml # 将 env 环境变量配置在了 ConfigMap 对象里
    kubectl apply -f laravel-deployment-and-service.yaml # 部署 Deployment 和 Service
    kubectl apply -f ingress.yaml # 部署 ingress 路由规则
    

    configmap.yaml 文件解读:

    kind: ConfigMap # 对象类型:配置
    apiVersion: v1 # api 版本
    metadata: # 原数据
      name: laravel-env # 对象名称
    data: # 变量数据,跟 laravel env 文件类似(不是太恰当的比方)
      APP_KEY: base64:zC8wVldUZfZJaGaZ7+CPh+5FzaXYmShm7G/Qh6GdRl8=
      APP_ENV: production
      DB_DATABASE: laravel
      DB_USERNAME: root
    

    laravel-deployment-and-service.yaml 文件解读:

    kind: Deployment # 对象类型
    apiVersion: apps/v1 # api 版本
    metadata: # 元数据
      name: laravel-deployment # 对象名称
      labels: # 对象标签
        app: laravel
    spec: # 对象规约
      selector: # 选择器
        matchLabels: # 标签匹配
          app: laravel
      replicas: 1 # 副本数量
      strategy: # 部署策略
        type: RollingUpdate # 滚动升级
        rollingUpdate: # 滚动升级的参数
          maxSurge: 1 # 在更新过程中最大同时创建 Pods 的数量
          maxUnavailable: 0 # 在更新过程中最大不可用的 Pods 的数量
      template: # 模版
        metadata: # 元数据
          labels: # 标签
            app: laravel 
        spec: # 对象规约
          containers: # 容器
            - name: laravel # # 容器名称
              image: jxlwqq/laravel-7-kubernetes-demo # 镜像
              ports: # 端口
                - containerPort: 80 # http web 服务默认端口 80
              env: # 环境变量,直接赋值
                - name: DB_HOST
                  value : mysql-service # !注意!这里的值,跟 MySQl Service 对象的 metadata name 一致
                - name: REDIS_HOST
                  value: redis-service # !注意!这里的值,跟 Redis Service 对象的 metadata name 一致
              envFrom: # 环境变量从哪里获得
                - configMapRef:
                    name: laravel-env # 从名称为 laravel-env 的 configMap 对象中获得
              readinessProbe: # 就绪探针,或者叫做就绪探测器,集群通过它可以知道容器什么时候准备好了并可以开始接受请求流量
                httpGet: # 判断项目首页是不是返回 20X 状态
                  port: 80
                  path: /
                  scheme: HTTP
              livenessProbe: # 存活探针,或者叫做存活探测器,集群通过它来知道什么时候需要重启容器
                httpGet: # 判断项目首页是不是返回 20X 状态
                  port: 80
                  path: /
                  scheme: HTTP
    ---
    kind: Service # 对象类型
    apiVersion: v1 # api 版本
    metadata: # 对象元数据
      name: laravel-service # 对象名称
      labels: # 标签
        app: laravel
    spec: # 对象规约
      selector: # 选择器
        app: laravel # 选择那些包含 app: laravel 标签的一组 Pods
      ports: # 端口
        - port: 80 # 暴露的端口
          targetPort: 80 # 与 上面的 Pod containerPort 一致
    

    ingress.yaml 文件解读:

    kind: Ingress # 对象类型
    apiVersion: networking.k8s.io/v1beta1 # api 版本,这个资源对象还处于 beta 版本,但是已经很稳定了,k8s 社区还是比较谨慎的
    metadata: # 元数据
      name: laravel-ingress # 对象名称
      labels: # 标签
        app: laravel
    spec: # 对象规约
      rules:
        - http:
            paths:
              - backend:
                  serviceName: laravel-service # 与 Laravel Service 对象的 metadata name 一致
                  servicePort: 80 # Service 端口,与 Laravel Service 对象的 port 一致
    

    创建 Ingress-nginx 控制器

    有了 Ingress 对象还不够,还需要 Ingress-nginx 控制器。这里又有一个不太好的比方了,Ingress 对象类似 Nginx 的 nginx.conf 文件,单单有配置文件是万万不行的,我们需要 Nginx 服务(软件)本身。

    为了让 Ingress 资源工作,集群必须有一个正在运行的 Ingress 控制器。 Kubernetes 官方目前支持和维护 GCE 和 nginx 控制器。

    这里我们选择 Ingress-nginx 控制器:

    cd ../ingress-nginx # 切换到 ingress-nginx 目录
    kubectl apply -f ingress-nginx-deployment-and-other-resources-mandatory.yaml
    kubectl apply -f ingress-nginx-service.yaml
    

    注:

    详细操作说明见:https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md

    访问

    curl http://localhost
    

    撒花,结束。

    最后的话

    现在的 Demo 将 web 服务、定时任务还有队列监听都放在了一个 Pod 中,无法对其进行扩容(因为定时任务和队列监听会重复)。如果需要对 Laravel 应用进行 HPA 扩容的话,还需要对 Laravel 项目的 docker/entrypint.sh 进行一些改造。将上述的 laravel-deploment 拆分成 3 个 Deploment,将容器分为三个角色,分为是 web、cron、queue。分别提供 web 服务、定时任务以及队列监听。最后对提供 web 服务的 Deploment 设置 HPA,根据 cpu 或者 内存占用率进行自动扩容。

    另外,定时任务也可以使用 Kubernetes 的 CronJob 对象来实现。

    这是后话,下期再细谈。

  • 相关阅读:
    Java 模板权重随机
    Linux java 启动脚本
    Resin Thread Dump
    Spring Cloud微服务实战 打造企业级优惠券系统 7-17 模板微服务网关路由配置定义
    Spring Cloud微服务实战 打造企业级优惠券系统 6-6 阶段总结 【优惠券系统业务思想与架构总结】
    Spring Cloud微服务实战 打造企业级优惠券系统 6-5 架构设计
    Spring Cloud微服务实战 打造企业级优惠券系统 5-5 阶段练习题
    Spring Cloud微服务实战 打造企业级优惠券系统 5-4 阶段说明[微服务通用模块说明]
    Spring Cloud微服务实战 打造企业级优惠券系统 5-3 统一异常代码编写
    Spring Cloud微服务实战 打造企业级优惠券系统 5-2 统一响应代码编写
  • 原文地址:https://www.cnblogs.com/jxlwqq/p/12569077.html
Copyright © 2011-2022 走看看