1. 前言
前段时间, 自己搞了个阿里云的服务器. 想自己在上面折腾, 但是不想因为自己瞎折腾而污染了现有的环境. 毕竟, 现在的阿里云已经没有免费的快照服务了. 要想还原的话, 最简单的办法就是重新装系统. 而一旦重装, 之前的搭建的所有环境就都白搭了.
再加上之前本身就想引入 docker, 所以就打算利用 docker 容器来部署这次的前端应用.
2. 构建前端应用
在打包之前, 首先需要一个可正常运行的前端应用. 这个可以使用 https://umijs.org/ 或者 https://facebook.github.io/create-react-app/ 来构建.
3.nginx 的默认配置文件
然后需要在项目中加上默认 nginx 配置文件.
- server {
- listen 80;
- server_name localhost;
- location / {
- root /usr/share/nginx/html;
- index index.HTML index.htm;
- try_files $uri $uri/ /index.HTML;
- }
- }
4. 编写本地构建脚本
4.1. 移除上次的目录和 Dockerfile
- #!/bin/bash
- if [ -d "./dist" ]; then
- rm -rf ./dist
- fi
- if [ -f "./Dockerfile" ]; then
- rm -f ./Dockerfile
- fi
因为每次更改后 dist 中的内容肯定与之前不同, 其实这一步显得不是那么必要. 运行 NPM 的打包命令也会自动清楚该目录.
而清除 Dockerfile 则是为了防止更新了 Dockerfile, 而这次却不能得到最新的配置.
4.2. 打包前端应用
执行前端的打包命令, 生成静态文件目录.
yarn build
4.3. 生成 Dockerfile
- echo "FROM nginx:latest">> ./Dockerfile
- echo "COPY ./dist /usr/share/nginx/html/">> ./Dockerfile
- echo "COPY ./default.conf /etc/nginx/conf.d/">> ./Dockerfile
- echo "EXPOSE 80">> ./Dockerfile
FROM 制定了该定制容器的基础镜像为 nginx:latest;COPY 命里将打包好的静态文件目录复制到容器内的 / usr/share/nginx/HTML / 目录下, 然后将 nginx 的配置写入容器中对应的位置; EXPOSE 则是设置对外暴露容器的 80 端口.
4.4. 生成并推送定制 image
- docker build -t detectivehlh/mine .
- docker login -u detectivehlh -p ********
- docker push detectivehlh/mine
这里是在开发本地, 使用 docker 命令来打包, 所以该脚本对 docker 有强依赖. build 命令表示打包 docker 应用的,-t 选项则制定了 docker 镜像的名字和 tag,tag 会默认为 latest.
然后登录 dockerHub, 将定制好的镜像推送到 dockerHub 中. detectivehlh 就是 dockerHub 的用户名, mine 是 image 的名字.
4.5. 删除 tag 为 none 的无用 image
第一次构建不会生成 tag 为 none 的 image, 但是后面每次再次执行该命令就会出现这样的情况. 所以每次构建了一个新的 image 后, 需要清除调不需要的 image.
docker images | grep none | awk '{print $3}' | xargs docker rmi
使用 grep 命令匹配到 tag 为 none 的 image,awk 是一个强大的文本分析工具,{print $3}表示打印出匹配到的每一行的第三个字段, 也就是 docker 的 image id. 如果是 $0 的话表示当前整行的数据.
xargs 是一个给其他命令 (也就是后面的 docker rmi) 传递参数的一个过滤器, 将标准输入转换成命令行参数.
总结来说, 上述命令就是找到 tag 为 none 的 image 的 ID, 然后使用 docker rmi 命令移除该 image.
4.6. 执行部署
- cmd="cd ~ && sh deploy.sh mine"
- SSH -t USER_NAME@IP_ADDRESS "bash -c \"${
- cmd
- }\""
通过 SSH 命令, 登录远程服务器, 并且执行参数中的脚本.
deploy.sh 是放在服务端的构建脚本. 放在默认的登录用户下. 我们发现, 后面还跟了个 mine, 这是在服务器上运行的 docker 镜像的名字. 这里暂时没有对 container 的名字加上 hash, 因为自己的小项目, 暂时没有必要.
在项目中的完整构建脚本如下.
- #!/bin/bash
- if [ -d "./dist" ]; then
- rm -rf ./dist
- fi
- if [ -f "./Dockerfile" ]; then
- rm -f ./Dockerfile
- fi
- yarn build
- echo "FROM nginx:latest">> ./Dockerfile
- echo "COPY ./dist /usr/share/nginx/html/">> ./Dockerfile
- echo "COPY ./default.conf /etc/nginx/conf.d/">> ./Dockerfile
- echo "EXPOSE 80">> ./Dockerfile
- docker build -t detectivehlh/mine .
- docker login -u detectivehlh -p ********
- docker push detectivehlh/mine
- docker images | grep none | awk '{print $3}' | xargs docker rmi
- cmd="cd ~ && sh deploy.sh mine"
- SSH -t USER_NAME@IP_ADDRESS "bash -c \"${cmd}\""
5. 编写服务器部署脚本
从上面步骤来看, 我们还需要一个服务器端的部署脚本. 大家可能会说, 标题不是说一个脚本搞定吗? em... 服务器一个, 本地一个... 简称只需一个脚本.
5.1 接收参数
在本地的构建脚本中, 我们传入了 docker 运行的 container 的名字. 在服务器构建脚本中需要来接收它. 然后更新刚刚推送的 docker image.
- #!/bin/bash
- name=$1
- docker pull detectivehlh/$name
5.2. 启动 container
在启动 container 时我们会面对两种情况, 名字为传入参数的 container 已经在运行了. 而在此时如果再次运行 docker run 命令就会报错而导致我们无法使用最新的 container, 也无法达到更新应用的目的.
- if docker ps | grep $name | awk {'print $(NF)'} | grep -Fx $name; then
- echo "Container mine is already start"
- docker stop $name
- docker rm $name
- docker run -d --name $name -p 3000:80 detectivehlh/$name
- else
- echo "Container mine is not start!, starting"
- docker run -d --name $name -p 3000:80 detectivehlh/$name
- echo "Finish starting"
- fi
- docker images | grep none | awk '{print $3}' | xargs docker rmi
所以在这里做一个判断, 第一个 if 判断如果存在名字为传入参数的 container 正在运行, 就停止当前容器再重新启动. 如果不存在则直接启动容器.
run 命令就不过多解释了.-d 表示后台运行容器并返回容器 ID,--name 表示设置容器的名字,-p 表示设置端口, 将阿里云服务器的 3000 端口映射到容器的 80 端口, 最后一句表示要启动哪个 image(好像还是解释了一遍).
最后一句就是移除多次更新后出现的 tag 为 none 的无用镜像. 完整的脚本如下.
- #!/bin/bash
- name=$1
- docker pull detectivehlh/$name
- if docker ps | grep $name | awk {'print $(NF)'} | grep -Fx $name; then
- echo "Container mine is already start"
- docker stop $name
- docker rm $name
- docker run -d --name $name -p 3000:80 detectivehlh/$name
- else
- echo "Container mine is not start!, starting"
- docker run -d --name $name -p 3000:80 detectivehlh/$name
- echo "Finish starting"
- fi
- docker images | grep none | awk '{print $3}' | xargs docker rmi
6. 如果你只是想打个包
看到标题进来的兄 dei, 如果只是想打包一个 docker 镜像, 那么你只需要 Dockerfile 文件和 docker build 命令就 OK 了.
7. 总结
最初写这个脚本, 主要目的是为了方便. 所以脚本中为了达到这个目的做了一些调整. 最终我达成了满足我需求的一个方便的部署脚本.
它的方便体现在, 当我完成了项目代码的更新, 只需要跑一下这个脚本, 然后等待一会儿, 项目就会自动打包成 docker image, 并且自动的在我的服务器上运行该 container.
但是这种方式会给实际的生产环境带来一些不可控的问题. 比如, 脚本必须不能上传, 因为涉及一些服务器的敏感信息. 但是如果你不小心上传了, 那你的服务器就相当于裸奔了; 再比如, 你对你的代码必须要十分自信, 没有经过测试的代码就直接部署, 会带来一些风险.
如果是自己用的, 那完全不用担心, 想怎么搞怎么搞. 但是如果是开放给所有人用的并且有一定的访问量, 比如博客, 那么对于其他用户来说, 这种方式就不怎么友好.
所以我的观点是, 分情况来. 目前来说我的项目只有少数几个人在用, 也还在处于迭代阶段. 并且代码仓库是私有的, 所以我完全不用担心隐私的问题. 服务未经测试就直接上线对于我来说, 其实问题也不大. 首先我会在本地测试, 确认无误后才会执行部署操作. 所以在不同的阶段, 找到最适合自己的方案就 OK.
来源: https://www.cnblogs.com/detectiveHLH/p/10756702.html