Gitlab: PHP项目CI/CD实践

小明 2025-05-05 17:32:31 4

���录

1 说明

2 CI/CD

2.1 部署方式一:增量部署

2.1.1 目标服务器准备 

2.2.2 Gitlab及Envoy脚本

2.2 部署方式二:镜像构建与部署

2.2.1 推送到私有化容器仓库

准备工作

脚本

要点

2.2.2 推送到hub.docker.com

准备工作

脚本

3 参考:


1 说明

  • 以一个laravel blog项目为例,做dev分支的CI/CD实践
  • 结合laravel envoy工具做多个远程服务器部署,分两种方式:A. 增量部署 B.镜像构建与部署

    服务器:

    SiteServerIP站点目录
    team1-prj2.dev.iahost001.dev.ia192.168.0.130/www/wwwroot/team1-prj2.dev.ia
    team1-prj2.dev.iahost002.dev.ia192.168.0.131/www/wwwroot/team1-prj2.dev.ia

    2 CI/CD

    2.1 部署方式一:增量部署

    通过Lavavel/Envoy和git拉取新版本文件进行部署

    2.1.1 目标服务器准备 
    • php8.2, 安装所需扩展,务必在cli下测试是否正常, 有些被disable的functions要打开
    • composer self-update, 兼容php8.2
    • 使用Laravel/Envoy分发部署,确保Envoy.blade.php是utf8格式文件
    • 站点目录结构
      root@host001:/www/wwwroot/team1-prj2.dev.ia# tree -d -L 3 ./
      ./
      ├── current -> /www/wwwroot/team1-prj2.dev.ia/releases/default
      ├── releases
      │   └── default
      │       ├── app
      │       ├── bootstrap
      │       ├── config
      │       ├── database
      │       ├── public
      │       ├── resources
      │       ├── routes
      │       ├── storage -> /www/wwwroot/team1-prj2.dev.ia/storage
      │       ├── tests
      │       └── vendor
      └── storage
          ├── app
          │   └── public
          ├── framework
          │   ├── cache
          │   ├── sessions
          │   ├── testing
          │   └── views
          └── logs
      

      说明:

      team1-prj2.dev.ia应用目录
      team1-prj2.dev.ia/releases版本发布目录,这里只设置了一个default目录,也可根据需要做日期变量发布
      team1-prj2.dev.ia/current

      链接到最新版本,被nginx访问的站点目录路径

      current -> /www/wwwroot/team1-prj2.dev.ia/releases/default/

      team1-prj2.dev.ia/storage

      链接到最新版本的应用数据保存目录,如:日志,缓存等

      storage -> /www/wwwroot/team1-prj2.dev.ia/storage

      team1-prj2.dev.ia/.dev

      .dev文件是运维人员建立的服务器定制环境文件,不进入仓库,链接到项目同名文件

      .env -> /www/wwwroot/team1-prj2.dev.ia/.env*

      team1-prj2.dev.ia/releases/default/.gitlab-ci.ymlgitlab 部署脚本
      team1-prj2.dev.ia/releases/default/Envoy.blade.phpenvoy 部署脚本

      nginx配置

      server
      {
          listen 80;
          server_name team1-prj2.dev.ia;
          index index.php index.html index.htm default.php default.htm default.html;
          root /www/wwwroot/team1-prj2.dev.ia/current/public;
          #CERT-APPLY-CHECK--START
          # 用于SSL证书申请时的文件验证相关配置 -- 请勿删除
          include /www/server/panel/vhost/nginx/well-known/team1-prj2.dev.ia.conf;
          #CERT-APPLY-CHECK--END
          #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
          #error_page 404/404.html;
          #SSL-END
          #ERROR-PAGE-START  错误页配置,可以注释、删除或修改
          #error_page 404 /404.html;
          #error_page 502 /502.html;
          #ERROR-PAGE-END
          #PHP-INFO-START  PHP引用配置,可以注释或修改
          include enable-php-82.conf;
          #PHP-INFO-END
          #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
          include /www/server/panel/vhost/rewrite/team1-prj2.dev.ia.conf;
          #REWRITE-END
          #禁止访问的文件或目录
          location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
          {
              return 404;
          }
          #一键申请SSL证书验证目录相关设置
          location ~ \.well-known{
              allow all;
          }
          #禁止在证书验证目录放入敏感文件
          if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
              return 403;
          }
          location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
          {
              expires      30d;
              error_log /dev/null;
              access_log /dev/null;
          }
          location ~ .*\.(js|css)?$
          {
              expires      12h;
              error_log /dev/null;
              access_log /dev/null;
          }
          
          location / {
              try_files $uri $uri/ /index.php?$query_string;
          }
          
          access_log  /www/wwwlogs/team1-prj2.dev.ia.log;
          error_log  /www/wwwlogs/team1-prj2.dev.ia.error.log;
      }
      2.2.2 Gitlab及Envoy脚本

      .gitlab-ci.yml

      # default:
      #  image: edbizarro/gitlab-ci-pipeline-php:7.4
      #default:
      # image: bennybi/php8.2
      # image: bennybi/php7.4
      stages:
        - test
        - deploy
      .init_ssh: &init_ssh |
        which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )
        eval $(ssh-agent -s)
        echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
        mkdir -p ~/.ssh
        chmod 700 ~/.ssh
        [[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
      cache:
        key: ${CI_COMMIT_REF_SLUG}
        paths:
          - vendor/
      unit_test:
        stage: test
        tags:
          - php
        script:
          - cp .env.test .env
          - composer install
          - composer global require "laravel/envoy"
          - php artisan key:generate
          - php artisan migrate
          - vendor/bin/phpunit
      deploy_dev:
        stage: deploy
        tags:
          - php
        environment:
          name: dev
          url: http://team1-prj2.dev.ia
        script:
          - *init_ssh
          - vendor/bin/envoy run deploy --branch="$CI_COMMIT_BRANCH" --commit="$CI_COMMIT_SHA"
        rules:
          - if: $CI_COMMIT_BRANCH == "dev"
      deploy_live:
        stage: deploy
        tags:
          - php
        script:
          - *init_ssh
          - vendor/bin/envoy run deploy --commit="$CI_COMMIT_SHA"
        environment:
          name: live
          url: http://team1-prj2.dev.ia
        when: manual
        rules:
          - if: $CI_COMMIT_BRANCH == "live"
      

      Envoy.blade.php

      @servers(['local' => 'deployer@host001.dev.ia','staging' => 'deployer@host002.dev.ia'])
      @setup
          $repository = 'git@host001.dev.ia:dev1/team1-prj2.git';
          $releases_dir = '/www/wwwroot/team1-prj2.dev.ia/releases';
          $app_dir = '/www/wwwroot/team1-prj2.dev.ia';
          $release = 'default';
          $new_release_dir = $releases_dir .'/'. $release;
          $user = get_current_user();
      @endsetup
      @story('deploy', ['on' => ['local','staging']])
          sync_repository
          run_composer
          update_symlinks
      @endstory
      @task('sync_repository')
          echo "Current User: {{$user}}, branch:{{$branch}}, commit:{{$commit}}"
          if [ -d "{{$new_release_dir}}" ]; then
              echo 'Pulling repository'
              cd {{ $new_release_dir }}
              git checkout {{ $branch }}
              git fetch
              git reset --hard HEAD
              git merge origin/{{ $branch }}
          else
              echo 'Cloning repository'
              [ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
              git clone --branch {{ $branch }}  --single-branch --depth 1 {{ $repository }} {{ $new_release_dir }}
              cd {{ $new_release_dir }}
              git reset --hard {{ $commit }}
              git config --global --add safe.directory '*'
          fi
      @endtask
      @task('run_composer')
          echo "Starting deployment ({{ $release }})"
          cd {{ $new_release_dir }}
          composer install --prefer-dist --no-scripts -q -o
      @endtask
      @task('update_symlinks')
          if [ ! -d "{{ $app_dir }}/current" ]; then
              echo "Linking storage directory"
              {{-- rm -rf {{ $new_release_dir }}/storage  --}}
              mv {{ $new_release_dir }}/storage {{ $app_dir }}
              ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
              
              echo 'Linking .env file'
              ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
              
              echo 'Linking current release'
              ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
              
              chmod 775 -Rf {{ $releases_dir }}
          fi
          chmod 775 -Rf {{ $new_release_dir }}/storage
          {{-- chown deployer:www -Rf {{ $new_release_dir }}/.git  --}}
      @endtask
      
      2.2 部署方式二:镜像构建与部署
      说明:
      1. 应用文件在build_image阶段,打包在镜像文件中,并推送到私有化的项目镜像仓库
      2. lavavel应用所需.env,不进入代码仓库,而是通过 -v 映射到具体路径部署,如示例代码中的 "-v /data0/Projects/team1-prj1/.env:/app/.env",防止敏感参数外泄
      3. 实例化后作为微服务容器,暴露8181端口提供外部访问
      2.2.1 推送到私有化容器仓库
      准备工作

      -  新建项目team1-prj1,初始化git 仓库为:http://host001.dev.ia:18181/dev1/team1-prj1.git

      -  建好容器仓库(见前文相关部分)

      -  需要在Admin Area->CI/CD->Variables添加docker访问用户变量

      $LOCAL_REGISTRY_LOGIN = your docker username
      $LOCAL_REGISTRY_PASSWORD
      脚本

       项目中添加Dockerfile 文件,这里用到的原型镜像是我之前定制的php8.2

      FROM bennybi/php8.2:latest
       
      WORKDIR /app
      RUN apt-get update -y && apt-get install -y openssl zip unzip git
      RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
      # RUN docker-php-ext-install pdo mbstring
      COPY .env.example .env
      COPY . /app
      RUN composer install --no-interaction --prefer-dist --optimize-autoloader
       
      # Run any additional commands specific to Laravel
      RUN php artisan config:cache
      RUN php artisan route:cache
      RUN php artisan view:cache
       
      CMD php artisan serve --host=0.0.0.0 --port=8181
      EXPOSE 8181

       .gitlab-ci.yml

      default:
        image: docker:19.03.8
        services:
          - mysql:5.7
        before_script:
          - docker info
      variables:
        # DOCKER_IMAGE_TAG: 'bennybi/team1-prj1'
        APP_NAME: 'team1-prj1'
        REGISTRY_URL: 'http://host001.dev.ia:5050'
        DOCKER_IMAGE_TAG: 'host001.dev.ia:5050/dev1/team1-prj1:1.0.0'
        # DOCKER_IMAGE_TAG: 'host001.dev.ia:5050/dev1/team1-prj1'
        CONTAINER_IMAGE_NAME: ${DOCKER_IMAGE_TAG}
        # MYSQL_DATABASE: test
        # MYSQL_ROOT_PASSWORD: fa843c707ce26702
        # DB_HOST: host001.dev.ia
        # DB_USERNAME: developer
      stages:
        - test
        - build
        - deploy
      unit_test:
        stage: test
        tags:
          - php
        script:
          - cp .env.test .env
          - composer install
          - php artisan key:generate
          - php artisan migrate
          - vendor/bin/phpunit
      build_image:
        stage: build
        tags:
          - php
        script:
          - docker login ${REGISTRY_URL} -u $LOCAL_REGISTRY_LOGIN -p $LOCAL_REGISTRY_PASSWORD
          - docker build --output type=registry,oci-mediatypes=false --cache-from "${DOCKER_IMAGE_TAG}" -t "${DOCKER_IMAGE_TAG}" --push --provenance=false .
          # - docker push ${DOCKER_IMAGE_TAG}:latest
          - docker push ${DOCKER_IMAGE_TAG}
        rules:
          - if: $CI_COMMIT_BRANCH == "dev"
      deploy_dev:
        stage: deploy
        tags:
          - php
        script:
          - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
          - eval $(ssh-agent -s)
          - ssh-add CI/CD->Variables添加docker访问用户变量 
      
      $DOCKER_LOGIN = your docker username
      $DOCKER_PASSWORD 
      脚本

      Dockerfile 

      同上 ...

      .gitlab-ci.yml

      default:
        image: docker:19.03.8
        services:
          - mysql:5.7
        before_script:
          - docker info
      variables:
        MYSQL_DATABASE: test
        MYSQL_ROOT_PASSWORD: fa843c707ce26702
        DB_HOST: host001.dev.ia
        DB_USERNAME: developer
      stages:
        - test
        - build
        # - deploy
      unit_test:
        stage: test
        tags:
          - php
        script:
          - cp .env.test .env
          - composer install
          - php artisan key:generate
          - php artisan migrate
          - vendor/bin/phpunit
      build_image:
        stage: build
        variables:
          DOCKER_IMAGE_TAG: 'bennybi/team1-prj1'
        tags:
          - php
        script:
          - docker build --cache-from "${DOCKER_IMAGE_TAG}" -t "${DOCKER_IMAGE_TAG}" .
          - docker login --username $DOCKER_LOGIN --password $DOCKER_PASSWORD
          # - docker run my-docker-image /script/to/run/tests
          - docker push ${DOCKER_IMAGE_TAG}:latest
        rules:
          - if: $CI_COMMIT_BRANCH == "dev"
      # deploy_dev:
      #   stage: deploy
      #   tags:
      #     - php
      #   script:
      #     - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
      #     - eval $(ssh-agent -s)
      #     - ssh-add 
The End
微信