• docker-compose实现容器任务编排


    项目开发中,往往都需要启动多个容器,容器之间又相互依赖,存在着启动的顺序。docker-compose就是可以通过命令控制依次启动容器。
    容器编排工具可以帮助我们批量地创建、调度和管理容器,帮助我们解决规模化容器的部署问题。
    Docker 三种常用的编排工具:Docker Compose、Docker Swarm 和 Kubernetes。

    本文主要是使用 docker-compose 实现react【nginx】、express【node】、mysql【mysql】搭建项目前端通过express访问mysql数据库的功能。

    项目目录结构

    .
    ├── docker-compose.yml
    ├── express-mysql
    │   ├── app.js
    │   ├── bin
    │   ├── Dockerfile
    │   ├── node_modules
    │   ├── package.json
    │   ├── public
    │   ├── routes
    │   ├── src
    │   ├── views
    │   └── yarn.lock
    ├── init.sql
    ├── react-express
    │   ├── Dockerfile
    │   ├── Dockerfile.build
    │   ├── node_modules
    │   ├── package.json
    │   ├── public
    │   ├── react-express.conf
    │   ├── src
    │   └── yarn.lock
    └── start.sh
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    创建前端项目

    使用 [create-react-app](https://create-react-app.bootcss.com/) 搭建前端项目,并安装axios插件,进行数据的调用。

    npx create-react-app react-express
    cd react-express
    yarn add axios
    npm start
    
    • 1
    • 2
    • 3
    • 4

    调整首页内容

    下载好项目,修改src/App.js的内容,实现可以请求后端接口返回的数据。

    import "./App.css";
    import { useState, useEffect } from "react";
    import axios from "axios";
    
    function App() {
      const [user, setUser] = useState([]);
      useEffect(() => {
        axios.get("http://localhost:8088/sql").then((res) => {
          setUser(res.data);
        });
      }, []);
      return (
        <div className="App">
          <header className="App-header">
            {user.map((u, i) => (
              <p key={i}> {u.username}</p>
            ))}
          </header>
        </div>
      );
    }
    
    export default App;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    添加Dockerfile文件

    使用Dockerfile构建前端镜像。

    FROM nginx
    COPY ./build /usr/share/nginx/html
    COPY ./react-express.conf /etc/nginx/conf.d
    WORKDIR /usr/share/nginx/html
    EXPOSE 80
    # 启动ngnix
    CMD ["nginx", "-g", "daemon off;"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    添加.dockerignore文件,排除不需要打包到docker镜像的文件

    node_modules
    # testing
    /coverage
    
    # misc
    .DS_Store
    .env.local
    .env.development.local
    .env.test.local
    .env.production.local
    
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    添加nginx配置文件

    添加react-express.conf文件

    server {
      listen 80;
      server_name localhost; # 如 www.baidu.com
      root /usr/share/nginx/html; # 指向打包后的目录
    
      location / {
        index index.html;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    生成前端镜像

    只需要将编译后的build文件添加到docker镜像中,生成镜像前,先执行下 npm run build

    npm run build
    docker build -t react-express .
    
    • 1
    • 2

    可以看到成功生成react-express镜像。并暴露端口为80,该镜像为后面执行docker-compose做准备。

    创建后端项目

    首先确保本地安装express;

    npm install -g express
    
    • 1

    使用[express-generator](https://www.expressjs.com.cn/starter/generator.html)生成器生产express-mysql项目

    mkdir express-mysql
    cd express-mysql
    npx express-generator
    
    • 1
    • 2
    • 3

    下载完成后,目录结构如下:

    .
    ├── Dockerfile
    ├── app.js
    ├── bin
    │   └── www
    ├── package.json
    ├── public
    │   ├── images
    │   ├── javascripts
    │   └── stylesheets
    ├── routes
    │   ├── index.js
    │   └── users.js
    ├── views
    │   ├── error.jade
    │   ├── index.jade
    │   └── layout.jade
    └── yarn.lock
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    为了连接mysql数据库,新增src目录结构

      .
      ├── Dockerfile
      ├── app.js
      ├── bin
      │   └── www
      ├── package.json
      ├── public
      │   ├── images
      │   ├── javascripts
      │   └── stylesheets
      ├── routes
      │   ├── index.js
      │   └── users.js
    + ├── src
    + │   ├── config.js
    + │   ├── db.js
      ├── views
      │   ├── error.jade
      │   ├── index.jade
      │   └── layout.jade
      └── yarn.lock
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    修改app.js设置跨域

    // 在路由调用之前设置
    app.all('*', function(req, res, next) {
        res.header("Access-Control-Allow-Origin", "*");
        res.header("Access-Control-Allow-Headers", "X-Requested-With");
        res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
        res.header("X-Powered-By",' 3.2.1')
        res.header("Content-Type", "application/json;charset=utf-8");
        next();
    });
    
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    连接mysql的配置

    src/config.js文件

    module.exports = {
      port: 3333, // express 服务启动端口
      /* 数据库相关配置 */
      db: {
        host: 'localhost', // 主机名
        port: 8808,        // MySQL 默认端口为 3306
        user: 'root',          // 使用 root 用户登入 MySQL
        password: '123456', // MySQL 密码,用你自己的
        database: 'blog', // 使用数据库
        useConnectionPooling: true, // 使用连接池进行连接,否则过一会node连接的mysql就会断开,出现错误
        connectionLimit: 50,
        queueLimit: 0,
        waitForConnection: true
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    src/db.js文件内容

    const mysql = require("mysql");
    // 获取数据库配置信息
    const config = require("./config").db;
    
    //创建一个mysql连接对象
    let connection;
    
    function handleError(err) {
      if (err) {
        // 如果是连接断开,自动重新连接
        if (err.code === "PROTOCOL_CONNECTION_LOST") {
          connect();
        } else {
          console.error(err.stack || err);
        }
      }
    }
    
    // 连接数据库
    function connect() {
      console.log("连接数据库 connect");
      //创建一个mysql连接对象
      connection = mysql.createPool(config);
      connection.getConnection(handleError);
      connection.on("error", handleError);
    }
    
    connect();
    module.exports = connection;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    进行sql查询

    routes/index.js下设置sql查询

    var express = require("express");
    var router = express.Router();
    const connection = require("../src/db");
    /* GET home page. */
    router.get("/sql", function (req, res, next) {
      /* 使用 connection.query 来执行 sql 语句 */
      // 第一个参数为 sql 语句,可以透过 js 自由组合
      // 第二个参数为回调函数,err 表示查询异常、第二个参数则为查询结果(这里的查询结果为多个用户行)
      connection.query("select username from user", (err, users) => {
        if (err) {
          res.send("query error"+ err);
        } else {
          // 将 MySQL 查询结果作为路由返回值
          res.send(users);
        }
      });
    });
    
    module.exports = router;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    构建后端镜像

    新建Dockerfile文件

    FROM node:16.11.0
    COPY ./ /app
    WORKDIR /app
    RUN npm config set registry https://registry.npm.taobao.org && npm install
    EXPOSE 3000
    CMD npm start
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    新建.dockerignore文件

    node_modules
    *.lock
    *.log
    yarn-error.log
    package-lock.json
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在当前目录下,执行docker构建镜像的命令,测试下是否有问题。

    docker build -t express-mysql .
    
    • 1

    初始化数据库

    创建sql脚本,在容器启动后给mysql容器初始化数据。【初始化数据有很多其它方案,可以在创建镜像时通过修改配置将初始化数据存入镜像,有兴趣可自行搜索】,在这里只使用简单的手动处理方案,降低认知难度。

    DROP TABLE IF EXISTS `user`;
    create table user
    (
        id       bigint auto_increment
                 primary key,
        username varchar(255) not null
    )
        collate = utf8mb4_unicode_ci;
    INSERT INTO blog.user (id, username) VALUES (1, 'admin');
    INSERT INTO blog.user (id, username) VALUES (2, 'docker');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    创建 mysql容器

    docker run -d --name mysql-user -p 8087:3306 \
    -v /data/docker-data/docker_mysql/conf:/etc/mysql/conf.d \
    -v /data/docker-data/docker_mysql/logs:/logs \
    -v /data/docker-data/docker_mysql/data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=123456 \
    mysql:5.7
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    登录mysql并初始化数据

    mysql -hlocalhost -P8087 -uroot -p123456 <<EOF
    use blog;
    source /data/compose/init.sql;
    quit
    EOF
    
    • 1
    • 2
    • 3
    • 4
    • 5

    docker-compose编排容器

    到目前已经完成了前端、后端、数据库的创建,项目执行依赖多个容器启动,上面的过程非常繁琐,而且容易出现错误。接下来就使用docker-compose可以使用一个命令将3个容器同时启动。

    安装[docker-compose](https://docs.docker.com/compose/)

    安装完成后查看版本信息

    docker-compose version
    ---
    docker-compose version 1.29.2, build unknown
    docker-py version: 5.0.3
    CPython version: 3.6.8
    OpenSSL version: OpenSSL 1.0.2k-fips  26 Jan 2017
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    新建yml文件

    docker-compose.yml是docker-compose启动的默认文件

    version: "3.1"
    services:
     react:
      build:
        context: "./react-express"
        dockerfile: Dockerfile
      depends_on:
       - express
      ports:
       - "8086:80"
     mysql:
      image: mysql:5.7
      container_name: mysql5
      ports:
       - "8087:3306"
      environment:
       MYSQL_ROOT_PASSWORD: 123456
       MYSQL_DATABASE: "blog"
      volumes:
       - "/data/docker-data/docker_mysql/conf:/etc/mysql/conf.d"
       - "/data/docker-data/docker_mysql/logs:/logs"
       - "/data/docker-data/docker_mysql/data:/var/lib/mysql"
     express:
      build:
        context: "./express-mysql"
        dockerfile: Dockerfile
      ports:
        - "8088:3000"
      depends_on:
       - mysql
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    设置完成 yml 文件,执行启动命令

    docker-compose up
    # 或者 后台启动
    docker-compose up -d
    
    • 1
    • 2
    • 3
    docker-compose ps
          Name                     Command               State                          Ports                       
    ----------------------------------------------------------------------------------------------------------------
    compose_express_1   docker-entrypoint.sh /bin/ ...   Up      0.0.0.0:8088->3000/tcp,:::8088->3000/tcp           
    compose_react_1     /docker-entrypoint.sh ngin ...   Up      0.0.0.0:8086->80/tcp,:::8086->80/tcp               
    mysql5              docker-entrypoint.sh mysqld      Up      0.0.0.0:8087->3306/tcp,:::8087->3306/tcp, 33060/tcp
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    一个命令就可以启动多个容器,并且组织好容器之间的依赖关系。

    继续优化

    创建前端镜像存在的问题.
    每次打包前端镜像时,需要先完成 build 的阶段,也就是前端编译的过程。该过程其实也可以自动处理。

    方案1:使用shell执行

    在项目目录下创建 start.sh 文件, 将前端编译的过程放到外部node环境【jenkins、或者服务器】执行。

    #!/bin/bash
    cd react-express
    yarn run build
    cd ..
    docker-compose up -d
    mysql -hlocalhost -P8087 -uroot -p123456 <<EOF
    use blog;
    source ./init.sql;
    quit
    EOF
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    方案2:采用镜像多阶段构建

    思路:将编译过程放到node环境中,打包出来build目录,然后将build目录拷贝到后边镜像内。

    新建一个Dockerfile.build文件。

    # 第一阶段,先对前端项目进行编译打包
    FROM node:16.11.0 AS nodeapp
    COPY ./ /app
    WORKDIR /app
    RUN yarn && yarn build
    
    #多阶段构建,最终会以最后的镜像作为基础镜像
    # 第二阶段,将打包的数据拷贝到后边镜像内,并创建nginx镜像
    FROM nginx
    COPY --from=nodeapp /app/build /usr/share/nginx/html
    COPY ./react-express.conf /etc/nginx/conf.d
    WORKDIR /usr/share/nginx/html
    EXPOSE 80
    # 启动ngnix
    CMD ["nginx", "-g", "daemon off;"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    然后同步修改下docker-compose.yml文件

    version: "3.1"
    services:
     react:
      build:
        context: "./react-express"
        dockerfile: Dockerfile.build
      depends_on:
       - express
      ports:
       - "8086:80"
     mysql:
      image: mysql:5.7
      container_name: mysql5
      ports:
       - "8087:3306"
      environment:
       MYSQL_ROOT_PASSWORD: 123456
       MYSQL_DATABASE: "blog"
      volumes:
       - "/data/docker-data/docker_mysql/conf:/etc/mysql/conf.d"
       - "/data/docker-data/docker_mysql/logs:/logs"
       - "/data/docker-data/docker_mysql/data:/var/lib/mysql"
     express:
      build:
        context: "./express-mysql"
        dockerfile: Dockerfile
      ports:
        - "8088:3000"
      depends_on:
       - mysql
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
  • 相关阅读:
    隐私计算技术创新及产业实践研讨会:学习
    VUE&Element
    12 原子性|可见性|有序性|JMM内存模型
    高并发系统架构设计之实战篇:消息流设计之拉模式
    基于51单片机的智能路灯控制系统proteus仿真原理图PCB
    第一章 Redis基础
    【Android进阶】6、多 Activity 间通信
    机器学习——特征工程
    【Spring框架】Spring概述及基本应用
    GS5MB-ASEMI贴片整流二极管GS5MB
  • 原文地址:https://blog.csdn.net/tuku0801/article/details/127842097