轻松部署 Laravel 应用 | 《03. 探寻一键脚本》

:clap: 本系列持续更新中,欢迎关注:https://github.com/wi1dcard/laravel-deploy...

由于课程上下文关联比较紧密,在开始前请先阅读 本文

你的支持是我写作的动力;关注我的客官们,请在右上角点个赞,将会让文章在首页展示,帮助更多人。

感谢 :clap: !

一键脚本里都做了什么?相信不少开发者有过这样的疑问,在完全地熟悉 Shell 语法之前,许多奇葩的语句命令很难完全读懂。在本节,我将大致剖析一键脚本对我们的服务器「进行了哪些改造」,为后文手动部署做好最基本的铺垫。

流程解析

几乎所有的一键脚本,无外乎就是帮我们做两件事:

  1. 安装运行环境
  2. 配置运行环境

对于上节我们使用的脚本,你可以在 这里 查看它的 安装 流程,在 这里 查看到 配置 流程。

  1. 初始化

    # 若出现错误则停止执行脚本
    set -e
    
    # 将变量 CURRENT_DIR 设置为脚本所在的目录
    CURRENT_DIR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
    
    # 引入一些位于 common.sh 的公共函数,类似 PHP 的 require
    source ${CURRENT_DIR}/../common/common.sh
    
    # 判断当前执行脚本用户是否为 root,若不是则报错并推出
    [ $(id -u) != "0" ] && { ansi -n --bold --bg-red "请用 root 账户执行本脚本"; exit 1; }
    
    # 将变量 MYSQL_ROOT_PASSWORD 设置为 random_string 命令的返回值,也就是随机字符串
    MYSQL_ROOT_PASSWORD=`random_string`
  2. 初始化系统

    function init_system {
        # 将系统本地化配置修改为 en_US.UTF-8
        export LC_ALL="en_US.UTF-8"
        echo "LC_ALL=en_US.UTF-8" >> /etc/default/locale
        locale-gen en_US.UTF-8
        locale-gen zh_CN.UTF-8
    
        # 设置系统时区为 Asia/Shanghai
        ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    
        # 「⚠️重点」更新本地软件源,可理解为刷新可安装软件的列表并记住它们的当前版本
        apt-get update
    
        # 安装名为 software-properties-common 的软件包,用于配置第三方软件源
        apt-get install -y software-properties-common
    
        # 调用 init_alias 函数
        init_alias
    }
    function init_alias {
        # 配置名为 sudowww 的别名,用于快速切换为 WWW_USER 变量所代指的用户
        alias sudowww > /dev/null 2>&1 || {
            echo "alias sudowww='sudo -H -u ${WWW_USER} sh -c'" >> ~/.bash_aliases
        }
    }
  3. 初始化软件源

    function init_repositories {
        # 「⚠️重点」添加第三方软件源,类似于配置 Packagist 镜像
        add-apt-repository -y ppa:ondrej/php
        add-apt-repository -y ppa:nginx/stable
        grep -rl ppa.launchpad.net /etc/apt/sources.list.d/ | xargs sed -i 's/ppa.launchpad.net/launchpad.proxy.ustclug.org/g'
    
        curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
        echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
    
        curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
        echo 'deb https://mirrors.tuna.tsinghua.edu.cn/nodesource/deb_8.x xenial main' > /etc/apt/sources.list.d/nodesource.list
        echo 'deb-src https://mirrors.tuna.tsinghua.edu.cn/nodesource/deb_8.x xenial main' >> /etc/apt/sources.list.d/nodesource.list
    
        # 配置软件源后立即更新
        apt-get update
    }
  4. 安装基础软件包

    function install_basic_softwares {
        # 「⚠️重点」
        # 安装 curl,一款命令行 HTTP 客户端,与 PHP 内的 cURL 扩展作用一致
        # 安装 git,可用于拉取源码等
        # 安装 build-essential,作为编译其它软件的必要基础环境
        # 安装 unzip,可用于解压 Zip 压缩包
        # 安装 supervisor,可用于保证 Laravel 队列处理器持续运行等
        apt-get install -y curl git build-essential unzip supervisor
    }
  5. 安装 PHP

    function install_php {
        # 「⚠️重点」安装 PHP 7.2、PHP 命令、PHP-FPM 以及一大堆常用 PHP 扩展。
        apt-get install -y php7.2-bcmath php7.2-cli php7.2-curl php7.2-fpm php7.2-gd php7.2-mbstring php7.2-mysql php7.2-opcache php7.2-pgsql php7.2-readline php7.2-xml php7.2-zip php7.2-sqlite3
    }
  6. 安装其它软件

    function install_others {
        # 「⚠️重点」由于需要安装 Nginx,故卸载 Apache
        apt-get remove -y apache2
    
        # 配置 MySQL 密码
        debconf-set-selections <<< "mysql-server mysql-server/root_password password ${MYSQL_ROOT_PASSWORD}"
        debconf-set-selections <<< "mysql-server mysql-server/root_password_again password ${MYSQL_ROOT_PASSWORD}"
    
        # 安装 Nginx、MySQL、Redis、Memcached、Beanstalkd、SQlite 3
        apt-get install -y nginx mysql-server redis-server memcached beanstalkd sqlite3
    
        # 「⚠️重点」将 /var/www 目录的拥有者和拥有组分别配置为 WWW_USER 和 WWW_USER_GROUP 变量的值,通常均为 www-data
        chown -R ${WWW_USER}.${WWW_USER_GROUP} /var/www/
    
        # 「⚠️重点」设置 Nginx 服务开机自启
        systemctl enable nginx.service
    }
  7. 安装 Node.js 和 Yarn

    function install_node_yarn {
        # 安装 Node.js 和 Yarn
        apt-get install -y nodejs yarn
        # 配置 Yarn 国内镜像源
        sudo -H -u ${WWW_USER} sh -c 'cd ~ && yarn config set registry https://registry.npm.taobao.org'
    }
  8. 安装 Composer

    function install_composer {
        # 「⚠️重点」下载 Composer 的 PHAR 文件
        wget https://dl.laravel-china.org/composer.phar -O /usr/local/bin/composer
        # 「⚠️重点」给予该文件可执行权限
        chmod +x /usr/local/bin/composer
        # 配置 Packagist 国内镜像
        sudo -H -u ${WWW_USER} sh -c  'cd ~ && composer config -g repo.packagist composer https://packagist.laravel-china.org'
    }
  9. 配置站点

    # 读入站点名(项目名)
    read -r -p "请输入项目名:" project
    
    # 确保项目名正常,若包含非法字符则报错并退出
    [[ $project =~ ^[a-zA-Z\0-9_\-\.]+$ ]] || {
        ansi -n --bold --bg-red "项目名包含非法字符"
        exit 1
    }
    
    # 读入站点域名
    read -r -p "请输入站点域名(多个域名用空格隔开):" domains
    
    # 拼接站点根目录
    project_dir="/var/www/${project}"
    
    # 输出信息,以便用户确认
    ansi -n --bold --green "域名列表:${domains}"
    ansi -n --bold --green "项目名:${project}"
    ansi -n --bold --green "项目目录:${project_dir}"
    
    # 确保用户确认后继续执行,否则退出
    read -r -p "是否确认? [y/N] " response
    case "$response" in
        [yY][eE][sS]|[yY])
            ;;
        *)
            ansi -n --bold --bg-red "用户取消"
            exit 1
            ;;
    esac
    
    # 「⚠️重点」读入 Nginx 配置模板,并替换站点名、域名、根目录
    # 随后写入实际配置文件 /etc/nginx/sites-available/<站点名>.conf
    cat ${CURRENT_DIR}/nginx_site_conf.tpl |
        sed "s|{{domains}}|${domains}|g" |
        sed "s|{{project}}|${project}|g" |
        sed "s|{{project_dir}}|${project_dir}|g" > /etc/nginx/sites-available/${project}.conf
    
    # 创建配置文件软链接,类似快捷方式
    ln -sf /etc/nginx/sites-available/${project}.conf /etc/nginx/sites-enabled/${project}.conf
    
    # 输出
    ansi -n --bold --green "配置文件创建成功";
    
    # 创建站点根目录,并分别配置目录自身的拥有者和拥有组为 WWW_USER 与 WWW_USER_GROUP 变量的值
    mkdir -p ${project_dir} && chown -R ${WWW_USER}.${WWW_USER_GROUP} ${project_dir}
    
    # 重启 Nginx 服务
    systemctl restart nginx.service
    
    # 输出
    ansi -n --bold --green "Nginx 重启成功";

以上便是整个脚本的核心代码;有些没看懂?没关系。你现在要做的是了解流程 —— 从干干净净的服务器到完整的运行环境,需要做什么?

因此,将以上代码简化、总结,可归纳为:

  1. 初始化系统并安装基础软件包
  2. 安装 Nginx
  3. 安装 PHP-FPM
  4. 安装 Git 和 Composer
  5. 安装其它可选软件
  6. 配置 Nginx 站点

依旧存在的问题

虽然一键脚本能够帮我们完成大多数环境安装与配置,相比 Web 面板在一定程度上更加安全,但它也存在着不可避免的问题:

  • 维护困难 —— 需编写大量判断语句,调试较为困难,且极易出现不兼容。
  • 无法定制 —— 难以处理环境定制化需求(例如测试环境与生产环境的不同配置),除非 Fork 修改源代码仓库并修改脚本。
  • 黑盒操作 —— 在脚本运行过程中,它做了什么?具体执行了那些命令?我们不得而知。
  • 安全堪忧 —— 市面上几乎所有一键脚本均强行要求根用户权限(最高管理员),有潜在安全隐患。
  • ...

在随后的课程中,你将会切身感受到 —— 无论是编写得多么完善、在多少台服务器上测试通过的一键脚本,都会有它十分受限的场景。实际上,目前已经存在不少方案能够完美地解决这些问题;不过,在开始讲解之前,我将先带领大家按照刚刚归纳的步骤,一步一步地进行手动部署,在这个过程中,希望你能够:

  • 巩固安装、配置环境的流程思路
  • 把流程转化为具体命令,在脑海中形成大概的印象

所谓「无他,唯手熟尔」,勤动手、切勿畏惧报错。如果你身处墙外,Google 和 StackOverflow 是你的好帮手;若是身处墙内,LearnKu(Laravel-China)也是不错的选择。善用搜索引擎和社区,或是在本课程内提问,我将尽我所能答疑解惑。

准备好开始探险吧!

记住 —— 熟能生巧

拓展:Cloud-Init

你是否听说过 Cloud-Init?它的原理便是在服务器启动(首次运行)时,载入一段用户自定义的脚本并执行,与一键脚本类似。但其运行过程完全无法调试,若出现问题只能在服务器启动后查看文件日志寻找线索,实在是万分痛苦。因此,本课程将不讲解采用 Cloud-Init 的部署方案,使用 Ansible 代替,敬请关注后续章节。

本作品采用《CC 协议》,转载必须注明作者和本文链接
Former WinForm and PHP engineer. Now prefer Golang and Rust, and mainly working on DevSecOps and Kubernetes.
本帖由系统于 5年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 5

@lovecn 正常情况下,需要。但由于外部有 $() 嵌套,Shell 将其认为是独立的语句进行解析,所以实际上,这条命令的执行顺序是:

  1. 获取 ${BASH_SOURCE[0]} 路径所在的目录:
dirname "${BASH_SOURCE[0]}"
  1. 切换到这个目录:
cd "某目录"
  1. 输出当前工作目录(也就是「某目录」):
pwd -P
  1. 设置 CURRENT_DIR 变量的值为 pwd 输出的值(实际上是 cd 命令 + pwd 命令输出的值,但是 cd 没有输出):
CURRENT_DIR=某目录

如果将引号转义,那么第一步中执行的语句将会变成:

dirname \"${BASH_SOURCE[0]}\"

你可以在 Bash 中执行试试看,多数情况会报错,除非你真的有个目录叫做 "xxx"(带引号)。

5年前 评论

兄弟 你这个系列真的写的非常棒 :+1:

5年前 评论

双引号里套双引号不用转义吗?
CURRENT_DIR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
67
粉丝
590
喜欢
1235
收藏
1133
排名:13
访问:32.4 万
私信
所有博文
社区赞助商