实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)

往期回顾

前端框架搭建——从零开始搭建一个高颜值后台管理系统全栈框架(一)

后端框架搭建——从零开始搭建一个高颜值后台管理系统全栈框架(二)

实现登录功能jwt or token+redis?——从零开始搭建一个高颜值后台管理系统全栈框架(三)

封装axios,让请求变得丝滑——从零开始搭建一个高颜值后台管理系统全栈框架(四)

背景

上一期我整了个服务器和域名,然后请教了我们公司的运维大佬,简单的实现了前后端自动化部署,只需要把代码push上去,然后就能自动编译和部署。这里没有用到k8s或Jenkins这种重量级的CI/CD工具,只使用了github actions和docker-compose,适合个人小项目使用。

使用github actions自动编译

介绍

GitHub Actions是GitHub提供的一种自动化工作流程(workflow)管理工具。它可以根据特定的事件触发,执行各种操作和任务,例如编译代码、运行测试、部署应用等。

使用GitHub Actions,开发者可以定义一个或多个工作流程,每个工作流程由一系列步骤(steps)组成。每个步骤可以包含命令行脚本、调用API、运行测试等任务。这些步骤可以在不同的操作系统环境下执行,如Linux、macOS和Windows。

GitHub Actions提供了一系列预定义的事件(events),如提交代码、创建分支、打标签等,当这些事件发生时,可以触发相应的工作流程执行。同时,开发者也可以通过手动方式触发工作流程的执行。

GitHub Actions还与其他工具和服务集成,例如Docker、AWS、Azure等,可以通过这些集成实现更复杂的自动化流程。

总之,GitHub Actions是一种灵活强大的自动化工作流程工具,使开发者能够轻松地设置和管理与代码相关的自动化任务,并提高开发效率和质量。

总结:我们使用github actions就不用自己手动在本地build代码和build镜像了,只需要往main分支推送代码就行了。

自动部署前端服务

介绍

如果想把前端打成Docker镜像,需要写在项目里编写Dockerfile。这里我就不介绍什么是docker了,如果对docker不了解的,可以从网上找相关资料学习了解一下docker,现在大部分公司都会选择把前后端部署到镜像里,方便维护和迁移。

在项目根目录下创建Dockerfile文件

这里就不多做解释了,大家看代码中的注视吧,很简单的。

# Dockerfile
# 因为我们项目使用的是pnpm安装依赖,所以找了个支持pnpm的基础镜像,如果你们使用npm,这里可以替换成node镜像
# FROM nginx:alpine
FROM gplane/pnpm:8.4.0 as builder

# 设置工作目录
WORKDIR /data/web

# 这里有个细节,为了更好的使用node_modules缓存,我们先把这两个文件拷贝到镜像中,镜像会检测发现这两个文件没有变化,就不会去重新安装依赖了。
COPY pnpm-lock.yaml .
COPY package.json .

# 安装依赖,如果上面两个文件没有改动,就不会重现安装依赖。
RUN pnpm install

# 把当前仓库代码拷贝到镜像中
COPY . .
# 运行build命令,可以替换成 npm run build
RUN pnpm run build
# 上面我们把代码编译完成后,会在镜像里生成dist文件夹。

# 下面我们把打包出来的静态资源放到nginx中部署
# 使用nginx做基础镜像
FROM nginx:alpine as nginx
# 设置时区
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone 
# 设置工作目录
WORKDIR /data/web
# 在nginx镜像中创建 /app/www文件夹
RUN mkdir -p /app/www
# 把上一步编译出来dist文件夹拷贝到刚才新建的/app/www文件夹中
COPY --from=builder /data/web/dist /app/www

# 暴露 80端口和443端口,因为我们服务监听的端口就是80,443端口是为了支持https。
EXPOSE 80 
EXPOSE 443

# 如果镜像中有nginx配置,先给删了
RUN rm -rf /etc/nginx/conf.d/default.conf
# 把项目里的./nginx/config.sh shell脚本复制到ngxin镜像/root文件夹下
COPY ./nginx/config.sh /root
# 给刚复制进去的shell脚本设置权限,让镜像启动的时候可以正常运行这个shell脚本。
RUN chmod +x /root/config.sh
# 镜像启动的时候运行config.sh脚本
CMD ["/root/config.sh"]

在项目中创建/nginx/config.sh文件

上一步我们说了,把项目里的/nginx/config.sh复制到nginx中,所以我们需要在项目里创建这个文件,这个文件其实就是nginx配置。

#! /bin/sh -e

cat >> /etc/nginx/conf.d/default.conf <<EOF
 
  server {
    listen      80;
    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    #gzip_http_version 1.0;
    gzip_comp_level 2;
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
    gzip_vary off;
    gzip_disable "MSIE [1-6].";

    proxy_read_timeout 600;

    location / {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
        if ($request_filename ~* .*\.(?:htm|html)$) 
		    {
			    add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
		    }

        root /app/www/;
        index index.html;
        client_max_body_size  500m;
    }

    location /api {
        proxy_pass  $SERVER_URL;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /file/ {
        proxy_pass $FILE_URL;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
 }

EOF

echo "starting web server"

nginx -g 'daemon off;'

很基本的nginx配置,这里有两个地方需要说一下。

  1. 为啥这里要写一个shell脚本而不是直接把配置复制到nginx中呢,是因为加了后端服务和文件服务的反向代理,为了不把反向代理的地址写死,(就是上面的$SERVER_URL$FILE_URL)我们需要使用环境变量,然后只能在shell脚本中才能使用环境变量,这两个环境变量在后面docker-compose里面去配置。
  2. if (\$request_filename ~* .*\.(?:htm|html)$)这一段配置是设置html文件不缓存,这样我们每次发布后,别人就不用清缓存后才能访问新的内容。html文件很小,所以只把html设置不缓存完全没问题,至于其他js,每次打包过后如果js文件有变化,他对应的hash也会变,如果文件内容不变,使用老的缓存也没问题。

编译镜像

上面两步做完,如果本地装了docker,已经可以在本地编译镜像了,在项目根目录下执行下面命令。

docker build -t test:v1.0.0 . 

test是镜像名,v1.0.0是版本号,后面那个点别忘记了。build成功后,我们可以使用docker desktop运行镜像也可以使用docker run -p 8000:80 test:v1.0.0命令去执行,现在执行肯定会报错的,因为有两个环境变量还没配呢。

编写github actions yml文件

基于上面我们可以本地编译镜像,然后把镜像推到镜像仓库,然后在服务器上拉镜像,最后运行镜像。这样虽然可以实现,但是每一步都是手动的,太不优雅了。这时候github actions登场了,用了它我们就可以解放自己的双手了,写完一个功能,代码一推,就可以去干别的事了。

在项目根目录下,创建.github/workflows/docker-publish.yml文件,文件名可以随便起。

name: Docker

on:
  push:
    branches: ['main']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Setup Docker buildx
        uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf

      - name: Cache Docker layers
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-

      - name: Log into registry ${{ env.REGISTRY }}
        if: github.event_name != 'pull_request'
        uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        id: build-and-push
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new

      - name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

      - name: SSH Command
        uses: D3rHase/ssh-command-action@v0.2.1
        with:
          HOST: ${{ secrets.SERVER_IP }}
          PORT: 22 
          USER: root
          PRIVATE_SSH_KEY: ${{ secrets.SERVER_KEY }}
          COMMAND: cd /root && ./run.sh

这些都是固定写法,兄弟们可以拿去直接用,有两个地方需要注意一下。

  1. branches: ['main']这个表示代码往main分支push后触发自动编译,可以改成别的,比如打版本,merge代码等。
  2. 最后后面SSH Command是用来执行服务器上/root/run.sh脚本的,因为目前我们使用github actions只做了编译代码和编译镜像以及上传镜像,还差一步自动部署镜像,这个脚本就是干这个的,我们后面再说。

我们可以再github这个标签里面看到执行进度,点进去可以看到日志。
实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)
编译完镜像后,github这里会显示你的镜像。
实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)
点进去可以看到完整的镜像名称,你可以去拉这个镜像在本地或服务器上部署,后面我们在部署的时候会用到这个镜像名称。
实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)

自动部署后端服务

编写后端Dockerfile文件

FROM gplane/pnpm:8.4.0 as builder

WORKDIR /app

# 先复制pnpm-lock.yaml和package.json文件,上文说过为了实现缓存
COPY pnpm-lock.yaml .
COPY package.json .

# 使用pnpm安装依赖
RUN pnpm install

COPY . .
# 编译代码
RUN pnpm run build

# 我们使用pm2启动后端服务,至于为什么,可以看下神光大佬的这篇文章。https://juejin.cn/post/7229595897813712957
FROM keymetrics/pm2:16-jessie

WORKDIR /app

COPY --from=builder /app/package.json ./
COPY --from=builder /app/pnpm-lock.yaml ./
COPY --from=builder /app/node_modules ./node_modules
ENV TZ="Asia/Shanghai"

RUN npm install pnpm -g
# node项目不像前端项目,编译代码的时候,会把node_modules里的文件也编译到dist中,后端还需要保留node_modules,不过我们可以把开发环境用到的包给移除掉,减少包的体积,pnpm install加--prod可以把开发环境中用到的包给移除掉,也就是安装在devDependencies中的依赖。
RUN pnpm install --prod

# 把需要的代码复制到镜像中
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/bootstrap.js ./
COPY --from=builder /app/script ./script
COPY --from=builder /app/src/config ./src/config
COPY --from=builder /app/tsconfig.json ./
COPY --from=builder /app/src/migration ./src/migration

EXPOSE 7001

# 启动项目
CMD ["npm", "run", "start"]

编写后端github actions文件

在项目根目录下,创建.github/workflows/docker-publish.yml文件,文件名可以随便起。文件内容和前端一模一样,直接把前端的拿过来就行了。

name: Docker

on:
  push:
    branches: ['main']
    tags: ['v*.*.*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Setup Docker buildx
        uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf

      - name: Cache Docker layers
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-

      - name: Log into registry ${{ env.REGISTRY }}
        if: github.event_name != 'pull_request'
        uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        id: build-and-push
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new

      - name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

      - name: SSH Command
        uses: D3rHase/ssh-command-action@v0.2.1
        with:
          HOST: ${{ secrets.SERVER_IP }}
          PORT: 22 # optional, default is 22
          # user of the server
          USER: root
          # private ssh key registered on the server
          PRIVATE_SSH_KEY: ${{ secrets.SERVER_KEY }}
          # command to be executed
          COMMAND: cd /root && ./run.sh

数据库迁移

我们在本地开发的时候,一般都是开启表结构自动同步,改了实体会自动把改的地方同步到数据库。但是线上环境最好不要开这玩意,很有可能会把线上的数据搞没,因为有时候你改字段名称,他自动同步的时候,会把原来的字段删掉,然后新建一个字段,这样那一列的数据就没了,线上开自动同步风险很大,建议不要开。

那我们不开自动同步,部署到其他环境的时候,怎么把当前的表结构同步过去呢,typeorm提供解决了方案,使用migration,本地改了实体结构后,执行一个命令,会自动生成一个迁移文件,然后我们发布到线上的时候,执行一下这个文件,就能把变更的内容同步到线上数据库了。这一块midway文档中也有介绍。

  1. 封装生成迁移的命令
node ./node_modules/@midwayjs/typeorm/cli.js migration:generate -d ./src/config/config.default.ts ./src/migration/migration
  1. 封装执行迁移的命令
node ./node_modules/@midwayjs/typeorm/cli.js migration:run -d ./src/config/typeorm.prod.ts
  1. 改造start命令,再执行迁移前执行了一个脚本,这个后面再说。
node ./script/init-database && npm run migration:run && NODE_ENV=production pm2-runtime start ./bootstrap.js --name midway_app -i 4
  1. 改造后的package.json scripts
    实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)
  2. 把typeorm的生产配置单独提出一个文件,里面用了很多环境变量,这样可以从外部注入配置值,而不是写死在代码中。
// src/config/typeorm.prod.ts
import { env } from 'process';

export default {
  typeorm: {
    dataSource: {
      default: {
        type: 'mysql',
        host: env.DB_HOST,
        port: env.DB_PORT || 3306,
        username: env.DB_USERNAME,
        password: env.DB_PASSWORD,
        database: env.DB_NAME || 'fluxy-admin',
        synchronize: false, // 关闭自动同步
        logging: false,
        // 扫描entity文件夹
        entities: ['**/entity/*{.ts,.js}'],
        timezone: '+00:00',
        // 迁移存在的文件路径
        migrations: ['**/migration/*.ts'],
        // 迁移存在的文件夹路径
        cli: {
          migrationsDir: 'migration',
        },
      },
    },
  },
};
  1. 实战。如果已经开发了一段时间,本地数据库中已经有了表结构,这时候我们需要把表全删了,然后执行生成迁移的命令,因为生成迁移的时候会对比当前代码中实体和数据库中表结构是否一样,有变化才会生成变更,所以把数据库中的表删除后,就可以生成一个完整的数据库表结构迁移脚本。执行npm run migration:generate命令,会生成下图中迁移文件,up是升级执行的方法,down是回滚执行的方法。可以看到up方法中生成了一些建表的语句,后面插入管理员账号是我手动加的,typeorm migration迁移只迁移表结构,数据不会帮你迁移,所以要自己写,数据迁移这一块很复杂,后面会单独写一篇文章介绍种子数据迁移。
    实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)
  2. 迁移流程。第一次迁移,线上数据库还没有建,这时候可以把本地数据库中的表删掉,然后运行npm run migration:generate:dev命令,会生成一个创建完整表结构的迁移,这时候如果要初始化数据,也可以写在里面。如果线上数据库已经建好了,后面改了实体,只需要提交代码的时候执行npm run migration命令生成迁移就行了,开发过程中不要多次执行这个命令,因为每次执行这个命令都会对比远程数据库和本地数据库的差异,生成变更,多次执行会生成重复的变更,比如加了一个表,每次执行都会生成创建表的更变,然后线上执行的时候就会报错,某某表已经存在,因为同一个表会建多次。
  3. 上面说了在执行start命令前,执行了一个node ./script/init-database命令,这个是用来检查线上有没有我们用的数据库,如果没有的话,需要自动建一个,不然启动后端服务会因为无法创建数据库连接而报错。说明一下这里创建数据库不是创建数据库服务,而是连上数据库服务后创建我们用到的数据库,对应sql是CREATE DATABASE 数据库名称。这个脚本还有一个作用,我们在使用docker-compose启动服务的时候,虽然设置了后端服务依赖数据库服务,数据库服务启动后,后端服务才会启动,但是只是让数据库服务先启动,而不是数据库服务启动完成后,再启动后端服务。所以如果数据库服务还没启动完成,这时候我们去连数据库就会失败,我在这个脚本中,写了个轮询检查数据库有没有启动完成,启动完成再去走后面的流程。
// script/init-database.js
const mysql = require('mysql2');

let count = 0;

function connect() {
  const host = process.env.DB_HOST;
  const user = process.env.DB_USERNAME;
  const password = process.env.DB_PASSWORD;
  const database = process.env.DB_NAME || 'fluxy-admin';

  const connection = mysql.createConnection({
    host,
    user,
    password,
  });
  connection.connect(error => {
    if (error) {
      console.log(`host: ${host}`);
      console.log(`user: ${user}`);
      console.log(`password: ${password}`);
      console.log('数据库连接失败,正在重新连接');
      console.log(error);

      setTimeout(() => {
        if (count >= 60) {
          console.log('数据库连接失败,请检查数据库服务是否正常启动。');
          return;
        }
        connect();
        count += 1;
      }, 1000);
      return;
    }
    connection.query(
      `SELECT * FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${database}'`,
      (err, result) => {
        if (err) {
          console.log(err);
          return;
        }
        if (result.length === 0) {
          console.log('检测到数据库不存在,正在为你创建数据库...');
          connection.query(`CREATE DATABASE \`${database}\``, () => {
            console.log('数据库创建成功');
            process.exit();
          });
        } else {
          process.exit();
        }
      }
    );
  });
  connection.end();
}

connect();

小结

到此我们前后端自动编译代码和生成镜像完成了,下面我们写如何自动在服务器上部署我们的服务。

使用docker compose自动部署

介绍

前面我们已经把前后端代码编译成了镜像,这时候我们可以在服务器上拉取镜像,然后启动镜像。前端还好,没有啥依赖,后端用到了数据库、redis、minio文件服务器,需要我们一个一个把这些服务部署好,后端才能启动。后面如果想迁移到一个新环境,还是需要一个一个部署,太麻烦了。有没有更好的方式呢,docker compose可以实现这个,我们可以把这些服务放到一个配置文件中统一管理,如果想迁移服务,直接在对应的服务器上运行一个命令就行了,是不是很方便,下面和大家说一下怎么使用。

环境准备

首先需要在服务器上安装docker和docker-compose。如何安装docker我就不说了,网上太多教程了,安装docker-compose可以用下面的命令。

curl -Lo /usr/bin/docker-compose ​https://ghproxy.com/https://github.com/docker/compose/releases/download/v2.11.2/docker-compose-linux-x86_64
chmod +x /usr/bin/docker-compose

编写docker-compose.yml文件

在服务器上/root文件夹下,创建docker-compose.yml文件

version: '3.7'
services:
  web:
    image: ghcr.dockerproxy.com/dbfu/fluxy-admin-web:main
    container_name: web
    restart: unless-stopped
    environment:
      SERVER_URL: http://server:7001
      FILE_URL: http://file:9000/
    ports:
      - 8080:80
    networks:
      - general

  server:
    image: ghcr.dockerproxy.com/dbfu/fluxy-admin-server:main
    container_name: server
    restart: unless-stopped
    environment:
      DB_HOST: db
      DB_PASSWORD: 123654
      DB_NAME: fluxy-admin
      REDIS_HOST: redis
      REDIS_PASSWORD: 123654
      MINIO_HOST: file
      MINIO_SECRET_KEY: 123654
      MINIO_PORT: 9000
    networks:
      - general
    depends_on:
      - db
      - redis
      - file

  db:
    image: mysql:latest
    container_name: db
    restart: unless-stopped
    volumes:
      - db:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: 123654
    networks:
      general:
        ipv4_address: 172.18.0.200

  redis:
    image: redis:latest
    container_name: redis
    command: --requirepass 123654
    restart: unless-stopped
    volumes:
      - redis:/data
    networks:
      general
        ipv4_address: 172.18.0.201

  file:
    image: minio/minio:latest
    container_name: file
    command: server --console-address :9001 /data
    restart: unless-stopped
    environment:
      MINIO_ROOT_USER: root
      MINIO_ROOT_PASSWORD: 123654
    volumes:
      - file:/data
      - /etc/localtime:/etc/localtime
    networks:
      - general

volumes:
  db:
  file:
  redis:

上面我们编排了5个服务,web、server、db、redis、file。

  • image:镜像名称,就是我们github上编译后的镜像,上面有说。这里有个注意的地方,因为我们的镜像推到了github的镜像仓库中,我们的服务器在国内访问还是有点慢的,我可以用github镜像仓库的代理地址ghcr.dockerproxy.com,拉取镜像的速度就会快很多。
  • container_name:容器名
  • command:执行对应的命令,有的服务有,有的服务没有
  • environment:环境变量,就是我们后端服务中,写的那些process.env.XXX,就是从这里取值的。
  • volumes:映射卷,把镜像里的目录映射到服务器上,不然每次重启服务,数据都丢了。有个小细节,举个例子,db的映射写的是db:/var/lib/mysql,正常来说前面的db应该是服务器的某个路径,但是如果我们写死了一个,服务器上没有这个目录就会报错了。所以在最外面定义了一个卷有db、file、redis,这样docker-compose启动的时候,会自动创建卷对应的目录,不用担心因为目录不存在而报错了。
  • networks:网络,如果服务都设置成同一个,上面配代理地址的时候,就不用写ip了,直接用服务名称。前端反向代理的地址就是用这种方式配的,SERVER_URL: http://server:7001。 还有一个问题,有的服务配置了ipv4_address,有的没配,是因为只发布后端的时候,如果不设置固定ip的话,后端服务的ip会变,而前端代理的地址还是老的,就会出现无法访问后端的情况。
  • ports:把容器内的端口映射到服务器上,在外部可以访问,正常来说只需要把前端的端口映射出来就行了,其他都通过服务名访问。如果你想远程访问数据库或其他服务,只需要给映射出来就行了。
  • depends_on:控制服务启动顺序

然后我们在当前目录下执行docker-compose pull && docker-compose up --remove-orphans命令就会自动部署服务了。如果以后我们想迁移,直接把这个docker-compose.yml文件复制过去,然后执行当前命令,就能实现一键迁移了。每次都写那么长的命令有点麻烦,为了方便我们写个shell脚本。在当前目录下创建run.sh文件,把上面命令复制到run.sh中,我们只需要执行sh run.sh就行了。

利用github actions实现自动部署

如果我们改了代码,重新编译了镜像,每次都需要在服务器上手动执行sh run.sh命令有点麻烦。不过github actions支持远程调用服务器上的命令,只需要配置服务器公网ip和密钥就行了。
上面github actions yml文件中我们已经提了这个。
实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)
在github上配置服务器ip和密钥,注意是密钥,不是密码,每个云服务器都可以生成密钥的。
实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)

自动绑定域名并支持https

如果想用你的域名访问web服务,并且想支持https,正常操作需要先申请证书,然后在nginx中配置你刚申请的证书,这些操作虽然不难,但是挺麻烦的。我请教了我们公司的运维大佬,他给我提供了个快捷方法,超级简单,这里分享给大家。

我们在docker-compose中添加两个服务,并在下面添加几个卷。这两个服务会自动申请证书,并配置nginx。

version: '3.7'
services:
  nginx-proxy:
    image: nginxproxy/nginx-proxy
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - conf:/etc/nginx/conf.d
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      - general

  acme-companion:
    image: nginxproxy/acme-companion
    container_name: nginx-proxy-acme
    environment:
      - DEFAULT_EMAIL=XXXXXX@163.com
    volumes_from:
      - nginx-proxy
    volumes:
      - certs:/etc/nginx/certs:rw
      - acme:/etc/acme.sh
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - general
      
...

volumes:
  conf:
  vhost:
  html:
  certs:
  acme:
  db:
  redis:
  file:

如果你想用这个的话,需要把那个XXXXXX@163.com邮箱改成自己的就行。改造一下web服务,加几个环境变量。
实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)
把上面的域名换成自己的就行了。

开启github action通知

github action执行流程的时候,成功后不会发送通知,只有失败才会,需要我们手动开启一下。
实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)
实现前后端全自动化部署,解放你的双手。——从零开始搭建一个高颜值后台管理系统全栈框架(五)
这个默认是勾上的,去掉就行了。

总结

好了到这里一套完整的自动化部署方案就完成了,兄弟们可以把这一套用在自己的个人项目中,还是挺方便的。

docker-compose模版仓库地址:github.com/dbfu/fluxy-…

前端仓库地址:github.com/dbfu/fluxy-…

后端仓库地址:github.com/dbfu/fluxy-…

原文链接:https://juejin.cn/post/7245613765693702201 作者:前端小付

(0)
上一篇 2023年6月18日 上午10:00
下一篇 2023年6月18日 上午10:11

相关推荐

发表回复

登录后才能评论