• Gitlab: PHP项目CI/CD实践


    目录

    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格式文件
    • 站点目录结构
      1. root@host001:/www/wwwroot/team1-prj2.dev.ia# tree -d -L 3 ./
      2. ./
      3. ├── current -> /www/wwwroot/team1-prj2.dev.ia/releases/default
      4. ├── releases
      5. │   └── default
      6. │   ├── app
      7. │   ├── bootstrap
      8. │   ├── config
      9. │   ├── database
      10. │   ├── public
      11. │   ├── resources
      12. │   ├── routes
      13. │   ├── storage -> /www/wwwroot/team1-prj2.dev.ia/storage
      14. │   ├── tests
      15. │   └── vendor
      16. └── storage
      17. ├── app
      18. │   └── public
      19. ├── framework
      20. │   ├── cache
      21. │   ├── sessions
      22. │   ├── testing
      23. │   └── views
      24. └── 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配置

    1. server
    2. {
    3. listen 80;
    4. server_name team1-prj2.dev.ia;
    5. index index.php index.html index.htm default.php default.htm default.html;
    6. root /www/wwwroot/team1-prj2.dev.ia/current/public;
    7. #CERT-APPLY-CHECK--START
    8. # 用于SSL证书申请时的文件验证相关配置 -- 请勿删除
    9. include /www/server/panel/vhost/nginx/well-known/team1-prj2.dev.ia.conf;
    10. #CERT-APPLY-CHECK--END
    11. #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
    12. #error_page 404/404.html;
    13. #SSL-END
    14. #ERROR-PAGE-START 错误页配置,可以注释、删除或修改
    15. #error_page 404 /404.html;
    16. #error_page 502 /502.html;
    17. #ERROR-PAGE-END
    18. #PHP-INFO-START PHP引用配置,可以注释或修改
    19. include enable-php-82.conf;
    20. #PHP-INFO-END
    21. #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
    22. include /www/server/panel/vhost/rewrite/team1-prj2.dev.ia.conf;
    23. #REWRITE-END
    24. #禁止访问的文件或目录
    25. location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
    26. {
    27. return 404;
    28. }
    29. #一键申请SSL证书验证目录相关设置
    30. location ~ \.well-known{
    31. allow all;
    32. }
    33. #禁止在证书验证目录放入敏感文件
    34. if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
    35. return 403;
    36. }
    37. location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    38. {
    39. expires 30d;
    40. error_log /dev/null;
    41. access_log /dev/null;
    42. }
    43. location ~ .*\.(js|css)?$
    44. {
    45. expires 12h;
    46. error_log /dev/null;
    47. access_log /dev/null;
    48. }
    49. location / {
    50. try_files $uri $uri/ /index.php?$query_string;
    51. }
    52. access_log /www/wwwlogs/team1-prj2.dev.ia.log;
    53. error_log /www/wwwlogs/team1-prj2.dev.ia.error.log;
    54. }
    2.2.2 Gitlab及Envoy脚本

    .gitlab-ci.yml

    1. # default:
    2. # image: edbizarro/gitlab-ci-pipeline-php:7.4
    3. #default:
    4. # image: bennybi/php8.2
    5. # image: bennybi/php7.4
    6. stages:
    7. - test
    8. - deploy
    9. .init_ssh: &init_ssh |
    10. which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )
    11. eval $(ssh-agent -s)
    12. echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
    13. mkdir -p ~/.ssh
    14. chmod 700 ~/.ssh
    15. [[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
    16. cache:
    17. key: ${CI_COMMIT_REF_SLUG}
    18. paths:
    19. - vendor/
    20. unit_test:
    21. stage: test
    22. tags:
    23. - php
    24. script:
    25. - cp .env.test .env
    26. - composer install
    27. - composer global require "laravel/envoy"
    28. - php artisan key:generate
    29. - php artisan migrate
    30. - vendor/bin/phpunit
    31. deploy_dev:
    32. stage: deploy
    33. tags:
    34. - php
    35. environment:
    36. name: dev
    37. url: http://team1-prj2.dev.ia
    38. script:
    39. - *init_ssh
    40. - vendor/bin/envoy run deploy --branch="$CI_COMMIT_BRANCH" --commit="$CI_COMMIT_SHA"
    41. rules:
    42. - if: $CI_COMMIT_BRANCH == "dev"
    43. deploy_live:
    44. stage: deploy
    45. tags:
    46. - php
    47. script:
    48. - *init_ssh
    49. - vendor/bin/envoy run deploy --commit="$CI_COMMIT_SHA"
    50. environment:
    51. name: live
    52. url: http://team1-prj2.dev.ia
    53. when: manual
    54. rules:
    55. - if: $CI_COMMIT_BRANCH == "live"

    Envoy.blade.php

    1. @servers(['local' => 'deployer@host001.dev.ia','staging' => 'deployer@host002.dev.ia'])
    2. @setup
    3. $repository = 'git@host001.dev.ia:dev1/team1-prj2.git';
    4. $releases_dir = '/www/wwwroot/team1-prj2.dev.ia/releases';
    5. $app_dir = '/www/wwwroot/team1-prj2.dev.ia';
    6. $release = 'default';
    7. $new_release_dir = $releases_dir .'/'. $release;
    8. $user = get_current_user();
    9. @endsetup
    10. @story('deploy', ['on' => ['local','staging']])
    11. sync_repository
    12. run_composer
    13. update_symlinks
    14. @endstory
    15. @task('sync_repository')
    16. echo "Current User: {{$user}}, branch:{{$branch}}, commit:{{$commit}}"
    17. if [ -d "{{$new_release_dir}}" ]; then
    18. echo 'Pulling repository'
    19. cd {{ $new_release_dir }}
    20. git checkout {{ $branch }}
    21. git fetch
    22. git reset --hard HEAD
    23. git merge origin/{{ $branch }}
    24. else
    25. echo 'Cloning repository'
    26. [ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
    27. git clone --branch {{ $branch }} --single-branch --depth 1 {{ $repository }} {{ $new_release_dir }}
    28. cd {{ $new_release_dir }}
    29. git reset --hard {{ $commit }}
    30. git config --global --add safe.directory '*'
    31. fi
    32. @endtask
    33. @task('run_composer')
    34. echo "Starting deployment ({{ $release }})"
    35. cd {{ $new_release_dir }}
    36. composer install --prefer-dist --no-scripts -q -o
    37. @endtask
    38. @task('update_symlinks')
    39. if [ ! -d "{{ $app_dir }}/current" ]; then
    40. echo "Linking storage directory"
    41. {{-- rm -rf {{ $new_release_dir }}/storage --}}
    42. mv {{ $new_release_dir }}/storage {{ $app_dir }}
    43. ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
    44. echo 'Linking .env file'
    45. ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
    46. echo 'Linking current release'
    47. ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
    48. chmod 775 -Rf {{ $releases_dir }}
    49. fi
    50. chmod 775 -Rf {{ $new_release_dir }}/storage
    51. {{-- chown deployer:www -Rf {{ $new_release_dir }}/.git --}}
    52. @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访问用户变量

    1. $LOCAL_REGISTRY_LOGIN = your docker username
    2. $LOCAL_REGISTRY_PASSWORD
    脚本

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

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

     .gitlab-ci.yml

    1. default:
    2. image: docker:19.03.8
    3. services:
    4. - mysql:5.7
    5. before_script:
    6. - docker info
    7. variables:
    8. # DOCKER_IMAGE_TAG: 'bennybi/team1-prj1'
    9. APP_NAME: 'team1-prj1'
    10. REGISTRY_URL: 'http://host001.dev.ia:5050'
    11. DOCKER_IMAGE_TAG: 'host001.dev.ia:5050/dev1/team1-prj1:1.0.0'
    12. # DOCKER_IMAGE_TAG: 'host001.dev.ia:5050/dev1/team1-prj1'
    13. CONTAINER_IMAGE_NAME: ${DOCKER_IMAGE_TAG}
    14. # MYSQL_DATABASE: test
    15. # MYSQL_ROOT_PASSWORD: fa843c707ce26702
    16. # DB_HOST: host001.dev.ia
    17. # DB_USERNAME: developer
    18. stages:
    19. - test
    20. - build
    21. - deploy
    22. unit_test:
    23. stage: test
    24. tags:
    25. - php
    26. script:
    27. - cp .env.test .env
    28. - composer install
    29. - php artisan key:generate
    30. - php artisan migrate
    31. - vendor/bin/phpunit
    32. build_image:
    33. stage: build
    34. tags:
    35. - php
    36. script:
    37. - docker login ${REGISTRY_URL} -u $LOCAL_REGISTRY_LOGIN -p $LOCAL_REGISTRY_PASSWORD
    38. - docker build --output type=registry,oci-mediatypes=false --cache-from "${DOCKER_IMAGE_TAG}" -t "${DOCKER_IMAGE_TAG}" --push --provenance=false .
    39. # - docker push ${DOCKER_IMAGE_TAG}:latest
    40. - docker push ${DOCKER_IMAGE_TAG}
    41. rules:
    42. - if: $CI_COMMIT_BRANCH == "dev"
    43. deploy_dev:
    44. stage: deploy
    45. tags:
    46. - php
    47. script:
    48. - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    49. - eval $(ssh-agent -s)
    50. - ssh-add <(echo "$SSH_PRIVATE_KEY")
    51. - mkdir -p ~/.ssh
    52. - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    53. - ssh deployer@host002.dev.ia "docker login ${REGISTRY_URL} -u $LOCAL_REGISTRY_LOGIN -p $LOCAL_REGISTRY_PASSWORD && docker pull $CONTAINER_IMAGE_NAME && docker stop ${APP_NAME} || true && docker rm ${APP_NAME} || true && docker run -d -p 8181:8181 -v /data0/Projects/${APP_NAME}/.env:/app/.env ${CONTAINER_IMAGE_NAME}"
    54. environment:
    55. name: dev
    56. url: http://team1-prj1.dev.ia
    57. # when: manual
    58. rules:
    59. - if: $CI_COMMIT_BRANCH == "dev"
    要点

    - 注意build时所需的参数,缺少会诱发错误: “Invalid tag: missing manifest digest”

    We used the buildx flag “–output type=registry,oci-mediatypes=false” to generate a Docker v2.2 manifest list.  We could set the value to “true” and generate an OCI manifest index, but the GitLab UI will incorrectly display “Invalid tag: missing manifest digest”. 

     推送结果图示:

    2.2.2 推送到hub.docker.com
    准备工作

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

    -  构建的镜像将push到hub.docker.com,因此需要在Admin Area->CI/CD->Variables添加docker访问用户变量

    1. $DOCKER_LOGIN = your docker username
    2. $DOCKER_PASSWORD
    脚本

    Dockerfile 

    同上 ...

    .gitlab-ci.yml

    1. default:
    2. image: docker:19.03.8
    3. services:
    4. - mysql:5.7
    5. before_script:
    6. - docker info
    7. variables:
    8. MYSQL_DATABASE: test
    9. MYSQL_ROOT_PASSWORD: fa843c707ce26702
    10. DB_HOST: host001.dev.ia
    11. DB_USERNAME: developer
    12. stages:
    13. - test
    14. - build
    15. # - deploy
    16. unit_test:
    17. stage: test
    18. tags:
    19. - php
    20. script:
    21. - cp .env.test .env
    22. - composer install
    23. - php artisan key:generate
    24. - php artisan migrate
    25. - vendor/bin/phpunit
    26. build_image:
    27. stage: build
    28. variables:
    29. DOCKER_IMAGE_TAG: 'bennybi/team1-prj1'
    30. tags:
    31. - php
    32. script:
    33. - docker build --cache-from "${DOCKER_IMAGE_TAG}" -t "${DOCKER_IMAGE_TAG}" .
    34. - docker login --username $DOCKER_LOGIN --password $DOCKER_PASSWORD
    35. # - docker run my-docker-image /script/to/run/tests
    36. - docker push ${DOCKER_IMAGE_TAG}:latest
    37. rules:
    38. - if: $CI_COMMIT_BRANCH == "dev"
    39. # deploy_dev:
    40. # stage: deploy
    41. # tags:
    42. # - php
    43. # script:
    44. # - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    45. # - eval $(ssh-agent -s)
    46. # - ssh-add <(echo "$SSH_PRIVATE_KEY")
    47. # - mkdir -p ~/.ssh
    48. # - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    49. # # - ~/.composer/vendor/bin/envoy run deploy --commit="$CI_COMMIT_SHA"
    50. # environment:
    51. # name: dev
    52. # url: http://team1-prj1.dev.ia
    53. # when: manual
    54. # rules:
    55. # - if: $CI_COMMIT_BRANCH == "dev"

    3 参考:

    GitLab: automated build and publish of multi-platform container image with GitLab pipeline | Fabian Lee : Software Engineer

    https://jaumemule.medium.com/build-a-php8-0-fpm-gitlab-ci-cd-application-docker-google-cloud-5f2868e8370

    https://docs.gitlab.com/ee/ci/docker/using_docker_build.html

     - https://github.com/papertank/envoy-deploy/blob/master/readme.md

    https://warrickbayman.medium.com/zero-downtime-laravel-deployments-with-envoy-version-2-227c8259e31c

    Test and deploy Laravel applications with GitLab CI/CD and Envoy | GitLab

    GitLab CI/CD examples | GitLab

    使用GitLab Runner为基于Laravel的PHP项目进行部署 

    GitLab CI/CD for Beginners [FREE Course] - DEV Community

  • 相关阅读:
    update会锁表吗?
    Tune-A-Video论文阅读
    为什么会突然牙疼?
    关于Spring遇到的各种问题
    企业征信牌照9月末盘点:149家机构荣获上榜,西藏等地机构待批
    如何搭建团队知识库?试试新的工具和方法吧!
    PHP实现DFA算法,查找关键词
    基于持续同调的在线社交网络传播研究
    微信小程序自定义图片上传组件
    [pytorchb笔记]03 --模型
  • 原文地址:https://blog.csdn.net/bennybi/article/details/136363745