用WSL+docker实现Web开发和一键部署

写在开头

前段时间参加了一场CTF入门级比赛,由于之前从来没有参加过类似的比赛,基本上零基础,全凭搜索引擎现学现卖,最终拿了个二等奖。
比赛结束后,突然觉得还是应该写一写东西,挂在网上。于是…
—— 后人有诗赞曰

栽树不忘挖井人

Overview

为方便后人查阅,这里简要概括一下:

  1. 开发主机为Windows,使用VSCode + WSL进行开发网站(基于LAMP)
  2. 采用容器技术,首先在本机完成开发和测试,然后可以随时打包更新到云服务器上
  3. 云服务器为CentOS7.6操作系统

对各个步骤的介绍都很详细。

主要任务为

把之前在腾讯云Windows Server2016中部署的WAMP网站(基于ThinkPHP开发)迁移到CentOS7.6下,并采用LAMP容器的方式部署,方便日后再次迁移。

Windows环境准备

安装WSL并在其中安装docker

具体安装教程可见WSL官方安装教程,这里我选择安装的是WSL2。
Windows Terminal可根据个人喜好安装,个人认为还是比较美观的:
用WSL+docker实现Web开发和一键部署

docker的安装教程和在正常Linux主机上安装无异,见docker官方安装教程
安装docker后配置源,这里用的是腾讯云源:

sudo vim /etc/docker/daemon.json

向其中添加如下内容:

{
"registry-mirrors": [
 "https://mirror.ccs.tencentyun.com"
]
}
使用VSCode连入WSL

写代码总不可能在终端里写。
首先安装插件:
用WSL+docker实现Web开发和一键部署
然后点击左下角这个绿色按钮,并选择New WSL Window:
用WSL+docker实现Web开发和一键部署
选择一个文件夹作为你的项目的文件夹。
用WSL+docker实现Web开发和一键部署

迁移网站到开发环境

这里我们先试着将网站迁移过来,并成功运行,然后仿照迁移过程来写Dockerfile。

  1. 选择基础镜像并创建容器:这里用的是docker hub上一个LAMP镜像,指定新建容器名称为dst-site,并将主机的8001端口映射到容器的80端口,9001端口映射到容器的3306端口,同时将容器内的网站根目录映射到WSL的开发环境中:
    sudo docker run -it --name dst-site -p "8001:80" -p "9001:3306" -v /home/liuyh/project/dst-site/app:/app mattrayner/lamp:latest-1804
    这里图方便肯定是直接全部集成到一个容器里头,以后有需求再考虑拆成多个容器。
    注意这条指令执行过程中会打印MySQL的初始账户:
    用WSL+docker实现Web开发和一键部署
    然后应该就可以在8081端口访问到网站了。并且也可以看到映射出来的网站根目录:
    用WSL+docker实现Web开发和一键部署
    Ctrl+P+Q退出容器。
  2. 修改MySQL账户密码:首先进入容器终端sudo docker exec -it dst-site /bin/bash。然后使用上一步得到的指令登录MySQL:
    用WSL+docker实现Web开发和一键部署
    添加root用户:
CREATE user 'root'@'%' identified by '密码';
GRANT all privileges on *.* to 'root'@'%';
  1. 迁移网站文件&配置环境:这里按照自己的需求对容器内进行改造就是了,网站、数据库、证书等都放进去就是了,这里记录几个点:
    (1) 进入容器终端然后cp /usr/share/zoneinfo/PRC /etc/localtime -f修改容器内的时区
    (2) 数据库(包括数据和用户)
    (3) 网站相关(记得改数据库连接密码)
    (4) 约定一个config文件夹,这个文件夹用来区分测试环境和运行环境
    (5) 添加定时任务更新网站数据:先输入命令crontab -e然后输入任务 * * * * * cd /app/cli/dstserverinfo && timeout 59s php main.php &> /dev/null(别忘了容器启动时必须执行命令service cron start
    (6) 启动Apache的https
    总之不断调整直到网站功能正常。

打包镜像

首先,保存我们刚刚做的那些更改:

sudo docker commit dst-site dst-site-proto

之后我们都是基于这个镜像,以及我们后续对网站做的更改来构建镜像。
编写Dockerfile

FROM dst-site-proto:latest

LABEL maintainer "your_name@example.com"

# 证书
COPY cert /etc/apache2/cert

# 网站配置
COPY 000-default.conf /etc/apache2/sites-available/000-default.conf

# 数据库
COPY mysql.zip /mysql.zip

# 启动容器时运行的文件
COPY run.sh /run.sh

# 网站
COPY app /app

WORKDIR "/"
VOLUME [ "/config" ]
EXPOSE 443

CMD ["/run.sh"]

run.sh可以可以在mattrayner/lamp提供的run.sh基础上进行修改:

#!/bin/bash
VOLUME_HOME="/var/lib/mysql"

if [[ ! -d $VOLUME_HOME/mysql ]]; then
    echo "=> An empty or uninitialized MySQL volume is detected in $VOLUME_HOME"
    echo "=> Loading initial database data"

    unzip -q -o /mysql.zip -d $VOLUME_HOME
    echo "=> Done!"
else
    echo "=> Using an existing volume of MySQL"
fi

echo "Setting up MySQL directories"
mkdir -p /var/run/mysqld

# Setup user and permissions for MySQL and Apache
chmod -R 770 /var/lib/mysql
chmod -R 770 /var/run/mysqld

if [ -n "$VAGRANT_OSX_MODE" ];then
    echo "Setting up users and groups"
    usermod -u $DOCKER_USER_ID www-data
    groupmod -g $(($DOCKER_USER_GID + 10000)) $(getent group $DOCKER_USER_GID | cut -d: -f1)
    groupmod -g ${DOCKER_USER_GID} staff
else
    echo "Allowing Apache/PHP to write to the app"
    # Tweaks to give Apache/PHP write permissions to the app
    chown -R www-data:staff /var/www
    chown -R www-data:staff /app
fi

echo "Allowing Apache/PHP to write to MySQL"
chown -R www-data:staff /var/lib/mysql
chown -R www-data:staff /var/run/mysqld
chown -R www-data:staff /var/log/mysql

if [ -e /var/run/mysqld/mysqld.sock ];then
    echo "Removing MySQL socket"
    rm /var/run/mysqld/mysqld.sock
fi

echo "Editing MySQL config"
sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/my.cnf
sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/mysql.conf.d/mysqld.cnf
sed -i "s/user.*/user = www-data/" /etc/mysql/mysql.conf.d/mysqld.cnf

echo "Starting cron"
service cron start

echo "Starting supervisord"
exec supervisord -n

这里最重要的是,当MySQL没有数据的时候,我们把预先准备好的数据解压过去,这样网站容器初始就可以正常运行,而之后再次部署容器时因为VOLUME/var/lib/mysql有数据了,就能在更新网站的同时保住网站的数据库数据。
后面的代码就是环境的准备和启动。

部署至云服务器

云服务器用的是腾讯云CentOS7.6,首先自然是得装Docker
准备好环境:
用WSL+docker实现Web开发和一键部署
这里debug文件里面得标记为False了,这样到时候挂载VOLUME的时候,把容器内的配置文件给覆盖了,自然就debug为False了。
然后我们还需要一个镜像仓库,这样我们可以在本地把构建好的镜像push上去,然后在远程主机里把这个镜像pull下来。可以去阿里云申请一个个人版(2021-6-5是免费的)镜像仓库:
用WSL+docker实现Web开发和一键部署
在阿里云镜像仓库中创建镜像仓库:
用WSL+docker实现Web开发和一键部署
点进去它会提示你怎么用命令行拉取镜像和上传镜像。
最后我们把镜像打包和部署的流程写成一个脚本:

#!/bin/bash
if [ ! $1 ]; then
    IMAGE='dst-site:latest'
else
    IMAGE='dst-site:'$1
fi
CONTAINER='dst-site-release'
HTTP_PORT='80'
HTTPS_PORT='443'
MYSQL_PORT='3306'
REMOTE_HOST='root@dst.liuyh.com'
PASSWD='远程主机密码'
DOCKER_REPO='仓库地址'
DOCKER_REPO_USER='仓库用户名'
DOCKER_REPO_PASSWD='该仓库用户的密码'

# 移除原容器和镜像
docker rmi $IMAGE -f &> /dev/null

# 构建镜像
cd /home/liuyh/project/dst-site
docker build -t $IMAGE .

# 上传镜像到仓库里
echo "TRANSFER IMAGE TO REPOSITORY"
REMOTE_IMAGE="$DOCKER_REPO/命名空间/$IMAGE"
docker login --username=$DOCKER_REPO_USER $DOCKER_REPO -p $DOCKER_REPO_PASSWD
docker tag $IMAGE $REMOTE_IMAGE
docker push $REMOTE_IMAGE
docker rmi $REMOTE_IMAGE

# 远程登录服务器拉取镜像并部署
echo "PULL IMAGE AND DEPLOY"
sshpass -p $PASSWD ssh -o StrictHostKeyChecking=no $REMOTE_HOST "docker login --username=$DOCKER_REPO_USER $DOCKER_REPO -p $DOCKER_REPO_PASSWD && \
    docker rm $CONTAINER -f &> /dev/null && \
    docker rmi $REMOTE_IMAGE -f &> /dev/null && \
    docker pull $REMOTE_IMAGE &&
    docker run -d --name $CONTAINER -p $HTTP_PORT:80 -p $HTTPS_PORT:443 -p $MYSQL_PORT:3306 \
    -v /root/project/dst-site/config:/config -v /root/project/dst-site/mysql:/var/lib/mysql $REMOTE_IMAGE"

这样整个一套部署流程就完毕,只需运行脚本,网站的更改就被打包上传并部署了。

后续维护

那么后续怎么维护这个开发流程呢?
如果只是对网站源代码/网站证书/vhost配置文件进行修改,那直接改好了运行脚本打包上传就是了。
网站的测试就直接用之前本地用mattrayner/lamp构建的dst-site容器。
如果要对镜像的其他部分进行修改可以进入容器内部修改,然后再重新docker commit构建一个dst-site-proto就行了。

总结

这篇文章不能说是非常专业吧,不过对于个人开发者自己开发一个单独的网站而言,确实是一个比较方便的选择,整体思路就是先在一个基础的LAMP容器内做一些配置和个性化修改,然后docker commit创建一个镜像作为个人网站的原型,然后再用Dockerfile在这个基础上进一步构建个人网站的镜像,Dockerfile这一步基本上只是把经常需要更改的内容和用来区分测试和开发环境的东西塞到原型里头,思路还是比较简单的。而之前用来构建原型的那个容器就用作后续的测试环境,如果丢失也可以用原型镜像来恢复。
docker这东西部署起来确实挺方便的,对于个人网站而言,其实全部应用程序打包到一个镜像里也没什么不好的,网站代码的维护和更新很方便。
写到这,倏然想起当年大三开发网站的时候,由于基础知识的缺乏,只能用Windows Server做开发环境,因为我当时只会在Windows Server上装一个PhpStorm然后在远程桌面上进行开发,开发完毕之后复制粘贴,改一改环境配置就算部署上去了。现在想起来着实有些滑稽。。。

上一篇:opencv——图像遍历以及像素操作


下一篇:tensor.max()