轻松部署 Laravel 应用 | 《03. 探寻一键脚本》
:clap: 本系列持续更新中,欢迎关注:https://github.com/wi1dcard/laravel-deploy...。
由于课程上下文关联比较紧密,在开始前请先阅读 本文。
你的支持是我写作的动力;关注我的客官们,请在右上角点个赞,将会让文章在首页展示,帮助更多人。
感谢 :clap: !
一键脚本里都做了什么?相信不少开发者有过这样的疑问,在完全地熟悉 Shell 语法之前,许多奇葩的语句命令很难完全读懂。在本节,我将大致剖析一键脚本对我们的服务器「进行了哪些改造」,为后文手动部署做好最基本的铺垫。
流程解析
几乎所有的一键脚本,无外乎就是帮我们做两件事:
- 安装运行环境
- 配置运行环境
对于上节我们使用的脚本,你可以在 这里 查看它的 安装 流程,在 这里 查看到 配置 流程。
-
初始化
# 若出现错误则停止执行脚本 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`
-
初始化系统
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 } }
-
初始化软件源
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 }
-
安装基础软件包
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 }
-
安装 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 }
-
安装其它软件
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 }
-
安装 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' }
-
安装 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' }
-
配置站点
# 读入站点名(项目名) 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 重启成功";
以上便是整个脚本的核心代码;有些没看懂?没关系。你现在要做的是了解流程 —— 从干干净净的服务器到完整的运行环境,需要做什么?
因此,将以上代码简化、总结,可归纳为:
- 初始化系统并安装基础软件包
- 安装 Nginx
- 安装 PHP-FPM
- 安装 Git 和 Composer
- 安装其它可选软件
- 配置 Nginx 站点
依旧存在的问题
虽然一键脚本能够帮我们完成大多数环境安装与配置,相比 Web 面板在一定程度上更加安全,但它也存在着不可避免的问题:
- 维护困难 —— 需编写大量判断语句,调试较为困难,且极易出现不兼容。
- 无法定制 —— 难以处理环境定制化需求(例如测试环境与生产环境的不同配置),除非 Fork 修改源代码仓库并修改脚本。
- 黑盒操作 —— 在脚本运行过程中,它做了什么?具体执行了那些命令?我们不得而知。
- 安全堪忧 —— 市面上几乎所有一键脚本均强行要求根用户权限(最高管理员),有潜在安全隐患。
- ...
在随后的课程中,你将会切身感受到 —— 无论是编写得多么完善、在多少台服务器上测试通过的一键脚本,都会有它十分受限的场景。实际上,目前已经存在不少方案能够完美地解决这些问题;不过,在开始讲解之前,我将先带领大家按照刚刚归纳的步骤,一步一步地进行手动部署,在这个过程中,希望你能够:
- 巩固安装、配置环境的流程思路
- 把流程转化为具体命令,在脑海中形成大概的印象
所谓「无他,唯手熟尔」,勤动手、切勿畏惧报错。如果你身处墙外,Google 和 StackOverflow 是你的好帮手;若是身处墙内,LearnKu(Laravel-China)也是不错的选择。善用搜索引擎和社区,或是在本课程内提问,我将尽我所能答疑解惑。
准备好开始探险吧!
记住 —— 熟能生巧。
拓展:Cloud-Init
你是否听说过 Cloud-Init?它的原理便是在服务器启动(首次运行)时,载入一段用户自定义的脚本并执行,与一键脚本类似。但其运行过程完全无法调试,若出现问题只能在服务器启动后查看文件日志寻找线索,实在是万分痛苦。因此,本课程将不讲解采用 Cloud-Init 的部署方案,使用 Ansible 代替,敬请关注后续章节。
本作品采用《CC 协议》,转载必须注明作者和本文链接
兄弟 你这个系列真的写的非常棒 :+1:
@Flourishing 多谢夸奖哈~
双引号里套双引号不用转义吗?
CURRENT_DIR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
@lovecn 正常情况下,需要。但由于外部有
$()
嵌套,Shell 将其认为是独立的语句进行解析,所以实际上,这条命令的执行顺序是:${BASH_SOURCE[0]}
路径所在的目录:pwd
输出的值(实际上是cd
命令 +pwd
命令输出的值,但是cd
没有输出):如果将引号转义,那么第一步中执行的语句将会变成:
你可以在 Bash 中执行试试看,多数情况会报错,除非你真的有个目录叫做
"xxx"
(带引号)。写得真棒