docker初使用

使用docker实现多机部署c++程序

步骤 1:准备你的 C++ 项目

假设你有一个简单的 C++ 项目,包含一个可执行文件 app,它可以通过命令行参数区分不同的节点行为。

C++ 示例代码 (main.cpp)

#include <iostream>
#include <string>

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "Usage: ./app <node_id>" << std::endl;
        return 1;
    }

    std::string node_id = argv[1];
    std::cout << "Node " << node_id << " is running." << std::endl;

    // 模拟节点行为
    while (true) {
        std::cout << "Node " << node_id << " is working..." << std::endl;
        sleep(5); // 每 5 秒打印一次
    }

    return 0;
}

编译代码

g++ main.cpp -o app

步骤 2:创建 Docker 镜像

为了在容器中运行你的 C++ 程序,需要创建一个 Docker 镜像。

Dockerfile 示例

# 使用官方的 Ubuntu 基础镜像
FROM ubuntu:20.04

# 安装必要的工具和依赖
RUN apt-get update && apt-get install -y \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# 将本地代码复制到容器中
COPY . /app
WORKDIR /app

# 编译 C++ 程序
RUN g++ main.cpp -o app

# 设置默认命令
CMD ["./app"]

构建镜像
在包含 Dockerfile 的目录下运行以下命令:

docker build -t cpp-app .

步骤 3:配置多容器部署

使用 docker-compose 来定义多个容器,并为每个容器传递不同的参数。

docker-compose.yml 示例

version: '3'
services:
  node1:
    image: cpp-app
    container_name: node1
    command: ["./app", "1"] # 启动时传递参数 "1"
    networks:
      - mynetwork

  node2:
    image: cpp-app
    container_name: node2
    command: ["./app", "2"] # 启动时传递参数 "2"
    networks:
      - mynetwork

networks:
  mynetwork:
    driver: bridge

步骤 4:启动容器

在包含 docker-compose.yml 文件的目录下运行以下命令:

docker-compose up

输出示例

Creating network "mynetwork" with driver "bridge"
Creating node1 ... done
Creating node2 ... done
Attaching to node1, node2
node1    | Node 1 is running.
node1    | Node 1 is working...
node2    | Node 2 is running.
node2    | Node 2 is working...
node1    | Node 1 is working...
node2    | Node 2 is working...

此时,两个容器分别运行了你的 C++ 程序,并且每个容器的行为由传入的参数决定。


步骤 5:验证网络通信(可选)

如果你的 C++ 程序需要网络通信,可以通过 Docker 的网络功能进行测试。

示例:让 Node 1 和 Node 2 相互通信

  1. 修改代码以支持网络通信(例如,使用 TCP/IP)。

  2. docker-compose.yml 中暴露端口:

    services:
      node1:
        image: cpp-app
        container_name: node1
        command: ["./app", "1"]
        ports:
          - "5000:5000" # 映射到主机的 5000 端口
        networks:
          - mynetwork
    
      node2:
        image: cpp-app
        container_name: node2
        command: ["./app", "2"]
        ports:
          - "5001:5001" # 映射到主机的 5001 端口
        networks:
          - mynetwork
    
  3. 在代码中实现客户端/服务器逻辑,确保它们可以通过容器的 IP 地址或服务名通信。


步骤 6:停止和清理

当测试完成后,可以停止并清理容器:

docker-compose down

总结

通过上述步骤,你可以轻松地使用 Docker 部署你的 C++ 项目,并模拟多机环境。以下是关键点:

  1. 使用 Dockerfile 构建镜像。
  2. 使用 docker-compose.yml 定义多个容器及其参数。
  3. 通过 Docker 的网络功能实现容器间的通信。

这种方法非常适合开发和测试分布式系统,同时具有轻量级和快速部署的优势。

常见错误

一般由文件名导致的错误

ERROR: failed to solve: the Dockerfile cannot be empty

这表明 Docker 在尝试构建镜像时,未能找到有效的 Dockerfile 文件,或者文件内容为空。以下是可能导致该问题的原因及解决方法。

1. 文件名错误

Docker 默认会在当前目录下寻找名为 Dockerfile 的文件(注意大小写)。如果你的文件名不是 Dockerfile,或者拼写有误(例如 dockerfileDockerFile),Docker 将无法识别。

解决方法

  • 确保文件名为 Dockerfile,且没有扩展名(如 .txt)。
  • 如果文件名不同,可以通过 -f 参数指定文件路径。例如:
    docker build -t my-image -f ./my-dockerfile .
    

2. 文件内容为空

如果 Dockerfile 文件存在,但内容为空或格式不正确,Docker 会报此错误。

解决方法

  • 打开 Dockerfile 文件,确保其内容有效。以下是一个简单的示例:

    # 使用官方的基础镜像
    FROM ubuntu:20.04
    
    # 安装必要的工具
    RUN apt-get update && apt-get install -y \
        build-essential \
        && rm -rf /var/lib/apt/lists/*
    
    # 复制本地代码到容器中
    COPY . /app
    WORKDIR /app
    
    # 编译 C++ 程序
    RUN g++ main.cpp -o app
    
    # 设置默认命令
    CMD ["./app"]
    
  • 确保文件保存后重新运行 docker build


3. 当前目录错误

Docker 构建命令需要在包含 Dockerfile 的目录下运行。如果你在错误的目录下执行 docker build,Docker 将找不到 Dockerfile

解决方法

  • 确保你在正确的目录下运行命令。例如:
    ls
    
    输出应包含 Dockerfile 文件。
  • 如果不在正确目录,切换到包含 Dockerfile 的目录:
    cd /path/to/your/project
    

4. 文件权限问题

在某些情况下,Dockerfile 文件可能由于权限问题无法被读取。

解决方法

  • 检查文件权限:
    ls -l Dockerfile
    
    输出应类似于:
    -rw-r--r-- 1 user group 123 Oct 10 12:34 Dockerfile
    
  • 如果权限不足,修改权限:
    chmod 644 Dockerfile
    

5. 隐藏字符或编码问题

有时,文件中可能存在隐藏字符(如 BOM)或编码问题,导致 Docker 无法正确解析 Dockerfile

解决方法

  • 使用文本编辑器(如 VS Code、Notepad++)打开 Dockerfile,确保文件编码为 UTF-8(无 BOM)。
  • 删除多余的空行或不可见字符。

验证修复

完成上述检查后,重新运行构建命令:

docker build -t my-image .

如果一切正常,你应该看到类似以下输出:

[+] Building 2.3s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                         0.1s
 => => transferring dockerfile: 32B                                          0.0s
 => [internal] load .dockerignore                                             0.0s
 => => transferring context: 2B                                              0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04              1.2s
 => [1/4] FROM docker.io/library/ubuntu:20.04                                0.0s
 => [2/4] RUN apt-get update && apt-get install -y build-essential           1.0s
 => [3/4] COPY . /app                                                        0.1s
 => [4/4] RUN g++ main.cpp -o app                                            0.5s
 => exporting to image                                                       0.3s
 => => exporting layers                                                      0.3s
 => => writing image sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx         0.0s
 => => naming to docker.io/library/my-image                                  0.0s

总结

通过以上步骤,应该能够解决 the Dockerfile cannot be empty 错误。以下是关键点:

  1. 确保文件名为 Dockerfile,且内容有效。
  2. 确保在正确的目录下运行 docker build
  3. 检查文件权限和编码,避免隐藏字符或格式问题。

镜像资源导致的问题

ERROR: failed to solve: ubuntu:20.04: failed to resolve source metadata for docker.io/library/ubuntu:20.04: failed to authorize: failed to fetch oauth token: Post "https://auth.docker.io/token": dial tcp 108.160.169.46:443: i/o timeout

这表明 Docker 在尝试拉取 ubuntu:20.04 镜像时,无法连接到 Docker Hub 的服务器。以下是可能导致该问题的原因及解决方法。


1. 网络连接问题

Docker 需要访问外部网络(如 Docker Hub)来拉取镜像。如果网络连接不稳定或受限,可能会导致超时错误。

解决方法

  • 检查你的网络连接是否正常:
    ping auth.docker.io
    
    如果无法连接,可能是网络问题。
  • 尝试更换网络环境(例如使用手机热点或其他 Wi-Fi)。
  • 如果你在公司或学校网络中,可能存在防火墙限制。联系网络管理员确认是否允许访问 docker.io
  • 一般需要配置代理环境
  1. 直接使用desktop拉取镜像

  • windows下有比较方便的代理工具,可以直接使用
  • 搜索·unbuntu·后pull 即可

验证修复

完成上述步骤后,重新运行构建命令:

docker build -t cpp-app .

如果一切正常,你应该看到类似以下输出:

[+] Building 1.8s (10/10) FINISHED                                                                                      docker:default
 => [internal] load build definition from Dockerfile                                                                              0.0s
 => => transferring dockerfile: 370B                                                                                              0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                                   0.0s
 => [internal] load .dockerignore                                                                                                 0.0s
 => => transferring context: 2B                                                                                                   0.0s
 => [1/5] FROM docker.io/library/ubuntu:20.04                                                                                     0.0s
 => [internal] load build context                                                                                                 0.0s
 => => transferring context: 459B                                                                                                 0.0s
 => CACHED [2/5] RUN apt-get update && apt-get install -y     build-essential     && rm -rf /var/lib/apt/lists/*                  0.0s
 => [3/5] COPY . /app                                                                                                             0.0s
 => [4/5] WORKDIR /app                                                                                                            0.0s
 => [5/5] RUN g++ main.cc -o app                                                                                                  0.5s
 => exporting to image                                                                                                            1.2s
 => => exporting layers                                                                                                           1.1s
 => => writing image sha256:2a52cf2f1053eee75b017d85f0b1e9b7ecdcc3fd1557f5632f76c652a0bddf08                                      0.0s
 => => naming to docker.io/library/cpp-app                                                      

为每个镜像设置专属ip

如果你想让两个节点(容器)拥有不同的 IP 地址,可以通过 Docker 的网络功能来实现。以下是具体的操作步骤和方法。


方法 1:使用自定义桥接网络

Docker 默认的桥接网络会自动为每个容器分配唯一的 IP 地址。你可以创建一个自定义的桥接网络,并确保每个容器在该网络中运行。

步骤 1:创建自定义桥接网络

运行以下命令创建一个自定义桥接网络:

docker network create mynetwork

步骤 2:启动容器并连接到网络

使用 docker run 命令启动容器,并将它们连接到自定义网络。例如:

docker run -d --name node1 --network mynetwork cpp-app ./app 1
docker run -d --name node2 --network mynetwork cpp-app ./app 2

步骤 3:查看容器的 IP 地址

运行以下命令查看容器的 IP 地址:

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' node1
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' node2

你会看到类似以下输出:

172.18.0.2
172.18.0.3

步骤 4:验证通信

在容器之间可以通过 IP 地址进行通信。例如,在 node1 中 ping node2 的 IP 地址:

docker exec -it node1 ping <node2-ip>

方法 2:使用 docker-compose 配置静态 IP 地址

如果你希望为每个容器指定固定的 IP 地址,可以在 docker-compose.yml 文件中配置。

步骤 1:创建自定义网络并分配子网

docker-compose.yml 文件中定义一个自定义网络,并为其分配子网。例如:

version: '3'
services:
  node1:
    image: cpp-app
    container_name: node1
    command: ["./app", "1"]
    networks:
      mynetwork:
        ipv4_address: 192.168.1.10

  node2:
    image: cpp-app
    container_name: node2
    command: ["./app", "2"]
    networks:
      mynetwork:
        ipv4_address: 192.168.1.11

networks:
  mynetwork:
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.1.0/24

步骤 2:启动服务

运行以下命令启动服务:

docker-compose up

步骤 3:验证 IP 地址

通过 docker inspect 或直接在容器中运行 ifconfig 查看 IP 地址。你会发现 node1node2 分别拥有 192.168.1.10192.168.1.11 的固定 IP 地址。

步骤 4:验证通信

node1 中尝试 ping node2 的 IP 地址:

可能遇到的错误

命令理解有误

bash: syntax error near unexpected token `newline'

是因为你在命令中直接使用了 <node2-ip>,而没有将其替换为实际的 IP 地址。<node2-ip> 是一个占位符,表示你需要将它替换为 node2 的实际 IP 地址。

步骤 1:获取 node2 的 IP 地址

运行以下命令查看 node2 的 IP 地址:

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' node2

假设输出为:

172.18.0.3

步骤 2:替换占位符

<node2-ip> 替换为实际的 IP 地址(例如 172.18.0.3)。完整的命令如下:

docker exec -it node1 ping 172.18.0.3

验证通信

如果一切正常,你应该看到类似以下输出:

PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.045 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.052 ms
...

这表明 node1node2 之间的网络通信是正常的。


注意事项

  1. 确保容器正在运行
    在执行 ping 命令之前,确保 node1node2 都在运行。可以通过以下命令检查:

    docker ps
    
  2. 确保网络配置正确
    如果 ping 失败,可能是网络配置有问题。请确认两个容器是否连接到同一个 Docker 网络。例如:

    docker network inspect mynetwork
    

    输出应包含 node1node2 的信息。

  3. 安装 ping 工具
    如果容器中没有安装 ping 工具,可能会报错。你可以进入容器并安装它:

    docker exec -it node1 bash
    apt-get update && apt-get install -y iputils-ping
    

如何退出 Docker 容器的交互式终端

退出 Docker 容器的交互式终端(bash 或其他 shell)非常简单。以下是几种常见的方法:


方法 1:使用快捷键 Ctrl + D

  • 在容器的交互式终端中,按下 Ctrl + D
  • 这会发送一个 EOF(End of File)信号,结束当前的 shell 会话并退出容器。

方法 2:输入 exit 命令

  • 在容器的交互式终端中,直接输入以下命令并按回车:
    exit
    
  • 这会显式地退出当前的 shell 会话,并返回到主机的终端。

方法 3:使用快捷键 Ctrl + PCtrl + Q(保持容器运行)

如果你希望退出交互式终端但让容器继续在后台运行,可以使用以下步骤:

  1. 按下 Ctrl + P,然后立即按下 Ctrl + Q
  2. 这会将容器从当前终端分离(detach),但容器仍然在后台运行。

验证容器是否仍在运行

docker ps

你会看到容器的状态为 Up

Docker 容器名称冲突:

Error response from daemon: Conflict. The container name "/node2" is already in use by container "dd6ef372ecf8...". You have to remove (or rename) that container to be able to reuse that name.

这表明 Docker 容器名称冲突了。具体来说,node2 这个名称已经被一个现有的容器占用,因此无法再次使用。

问题原因

  1. 之前运行的容器未清理
    你可能之前运行过 docker-compose up,但没有正确停止和删除容器。
  2. 手动创建了同名容器
    你可能手动运行了 docker run 命令,并指定了相同的容器名称(如 node2)。

步骤 1:检查现有容器

运行以下命令查看当前运行的容器:

docker ps -a

输出示例:

CONTAINER ID   IMAGE         COMMAND                  CREATED        STATUS        NAMES
dd6ef372ecf8   cpp-app       "./app 2"                2 hours ago    Exited        node2
abc12345def6   cpp-app       "./app 1"                2 hours ago    Up 2 hours    node1

从输出中可以看到,node2 容器已经存在,可能是之前运行后未删除。


步骤 2:删除冲突的容器

如果 node2 已经存在且不再需要,可以将其删除:

docker rm node2

如果你想强制删除正在运行的容器,可以添加 -f 参数:

docker rm -f node2

步骤 3:重新启动服务

删除冲突的容器后,重新运行以下命令启动服务:

docker-compose up

关于 version 警告

在你的日志中还有一条警告:

WARN[0000] /root/project/docker/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion

这表明你使用的 docker-compose.yml 文件中的 version 字段已经过时。如果你使用的是较新的 Docker Compose 版本(v2 或更高),可以直接移除 version 字段。

posted @ 2025-02-21 13:33  daligh  阅读(23)  评论(0)    收藏  举报