[docker] 网络连接

使用 docker 容器会遇到下面 3 种情况:

  1. 容器与万维网之间的交流

    默认情况下是允许的

  2. 从容器到本机的交流

    假设有一些服务运行在本机,如数据库,而 docker 需要和本机进行交流去获取数据

    这个实现需要配置

  3. 容器与容器之间的交流

    这个是比较常见的用法了,同样需要配置

项目配置

配置一个 node 项目去运行一下项目

代码

app.js:

const express = require("express");
const bodyParser = require("body-parser");
const axios = require("axios").default;
const mongoose = require("mongoose");

const Favorite = require("./models/favorite");

const app = express();

app.use(bodyParser.json());

app.get("/favorites", async (req, res) => {
  const favorites = await Favorite.find();
  res.status(200).json({
    favorites: favorites,
  });
});

app.post("/favorites", async (req, res) => {
  const favName = req.body.name;
  const favType = req.body.type;
  const favUrl = req.body.url;

  try {
    if (favType !== "movie" && favType !== "character") {
      throw new Error('"type" should be "movie" or "character"!');
    }
    const existingFav = await Favorite.findOne({ name: favName });
    if (existingFav) {
      throw new Error("Favorite exists already!");
    }
  } catch (error) {
    return res.status(500).json({ message: error.message });
  }

  const favorite = new Favorite({
    name: favName,
    type: favType,
    url: favUrl,
  });

  try {
    await favorite.save();
    res
      .status(201)
      .json({ message: "Favorite saved!", favorite: favorite.toObject() });
  } catch (error) {
    res.status(500).json({ message: "Something went wrong." });
  }
});

app.get("/movies", async (req, res) => {
  try {
    const response = await axios.get("https://swapi.dev/api/films");
    res.status(200).json({ movies: response.data });
  } catch (error) {
    res.status(500).json({ message: "Something went wrong." });
  }
});

app.get("/people", async (req, res) => {
  try {
    const response = await axios.get("https://swapi.dev/api/people");
    res.status(200).json({ people: response.data });
  } catch (error) {
    res.status(500).json({ message: "Something went wrong." });
  }
});

mongoose.connect(
  "mongodb://localhost:27017/swfavorites",
  { useNewUrlParser: true },
  (err) => {
    if (err) {
      console.log(err);
    } else {
      app.listen(3000);
    }
  }
);

下面这个是 mongoose 的配置:

const { Schema, model } = require("mongoose");

const favoriteSchema = new Schema({
  type: String, // 'movie' | 'character'
  name: String,
  url: String,
});

const Favorite = model("Favorite", favoriteSchema);

module.exports = Favorite;

Dockerfile:

FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

CMD ["node", "app.js"]

简单解释一下这个代码就是,它会创立几个 endpoints,有两个 endpoints 会直接与进行沟通 https://swapi.dev/api/films,获取数据。还会有两个 endpoints 与 mongodb 进行沟通,进行 POST 和 GET 的 request

其中 https://swapi.dev/api/films 是一个 dummy API endpoint,是别人在网上 host 的:

[docker] 网络连接-LMLPHP

而 mongodb 则是本机上进行安装,或者使用 docker 容器进行实现

下面会提一下怎么配置本机,但是这里只会运行容器与容器之间的沟通

docker build

build 过程会报错:

docker build -t favorite-app .
[+] Building 7.3s (10/10)docker run --name favorites --rm -p 3000:3000 favorite-app
(node:1) [MONGODB DRIVER] Warning: Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
(Use `node --trace-warnings ...` to show where the warning was created)
MongoNetworkError: failed to connect to server [localhost:27017] on first connect [Error: connect ECONNREFUSED 127.0.0.1:27017
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1605:16) {
  name: 'MongoNetworkError'
}]
    at Pool.<anonymous> (/app/node_modules/mongodb/lib/core/topologies/server.js:441:11)
    at Pool.emit (node:events:519:28)
    at /app/node_modules/mongodb/lib/core/connection/pool.js:564:14
    at /app/node_modules/mongodb/lib/core/connection/pool.js:1000:11
    at /app/node_modules/mongodb/lib/core/connection/connect.js:32:7
    at callback (/app/node_modules/mongodb/lib/core/connection/connect.js:300:5)
    at Socket.<anonymous> (/app/node_modules/mongodb/lib/core/connection/connect.js:330:7)
    at Object.onceWrapper (node:events:634:26)
    at Socket.emit (node:events:519:28)
    at emitErrorNT (node:internal/streams/destroy:169:8)
    at emitErrorCloseNT (node:internal/streams/destroy:128:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

出错的方式可能有两个:

  1. 本机没有安装 mongodb

  2. 安装了但是没有配置好

这里将 mongodb 连接的部分注释掉,重新运行一下:

[docker] 网络连接-LMLPHP

发现 docker 容器和 https://swapi.dev/api/films 的交流沟通是没有任何问题的

运行 mongodb 镜像

这里运行一下结果就好了:

docker run -d --name mongodb mongo
# skip downloading process
Status: Downloaded newer image for mongo:latest
fb63b699a8ed81852c67057c3485ee4698be1437c3e6bef2bc3c87a1eca9a810

本机与容器交流

这里只要修改代码就好了:

mongoose.connect(
  "mongodb://host.docker.internal:27017/swfavorites",
  { useNewUrlParser: true },
  (err) => {
    if (err) {
      console.log(err);
    } else {
      app.listen(3000);
    }
  }
);

localhost 改成 host.docker.internal 即可

容器之间的沟通

这个有两种方法

直接沟通

第一个直接获取容器的 IP 地址:

# checked the exposed IP address by mongodocker container inspect mongodb | grep "IPAddress"
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.2",
                    "IPAddress": "172.17.0.2",

然后更新代码:

mongoose.connect(
  "mongodb://172.17.0.2:27017/swfavorites",
  { useNewUrlParser: true },
  (err) => {
    if (err) {
      console.log(err);
    } else {
      app.listen(3000);
    }
  }
);

这个缺点在于每次重新运行容器的时候,IP 地址可能会出现改变,因此就需要修改代码,再重新 build

使用 docker networks

使用 network 可以让 docker 去管理 IP 地址,而非需要手动管理,这里第一步需要先通过 --network <network_name> 去创建一个 network

和 volume 不一样,docker 没有办法在容器运行的时候自动创建 network,所以先创建一个 network 是非常重要的事情

在 network 创建了之后,docker 会自动找寻连接在当前 network 上的容器并完成沟通

重新配置 mongo

如果在没有创建 network 的情况下直接运行 --network 就会报错:

docker run -d --rm --name mongodb --network favorites-net mongo
60cdc3029a12b8e6af46728ce648598fa987977df2e8bf9f729596436266c24b
docker: Error response from daemon: network favorites-net not found.

所以还是需要手动先创立一个 network:

docker network create favorites-net
f9385f787df37b608c6bc8bfb4619ff979c44312ee1f886965e17551dbde5d26
❯ docker network ls
NETWORK ID     NAME            DRIVER    SCOPE
624223e7a219   bridge          bridge    local
f9385f787df3   favorites-net   bridge    local
e0b7d35ecfa6   host            host      local
6592d848be44   none            null      localdocker run -d --rm --name mongodb --network favorites-net mongo
ac07e3660d2f04c515b7f635c37ae3e3e728f1c09c8afa9dfa0d64eb8e4cfe93
❯ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS       NAMES
ac07e3660d2f   mongo     "docker-entrypoint.s…"   11 seconds ago   Up 10 seconds   27017/tcp   mongodb
❯ docker container inspect mongodb | grep "net"
            "NetworkMode": "favorites-net",
            "SandboxKey": "/var/run/docker/netns/29cafc1c1f7a",
                "favorites-net": {
更新 server 代码连接 network

这里的变化是把 ip 地址改成容器名称:

mongoose.connect(
  "mongodb://mongodb:27017/swfavorites",
  { useNewUrlParser: true },
  (err) => {
    if (err) {
      console.log(err);
    } else {
      app.listen(3000);
    }
  }
);

随后重新 build 和运行:

docker run --name favorites --rm --network favorites-net -p 3000:3000 favorite-app
(node:1) [MONGODB DRIVER] Warning: Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
(Use `node --trace-warnings ...` to show where the warning was created)

最终可以完成 CRUD 的操作:

[docker] 网络连接-LMLPHP

[docker] 网络连接-LMLPHP

04-23 12:53