Linking

Capturing Life & Tech

  • 主页
  • 随笔
  • 关于我
所有文章 外链

Linking

Capturing Life & Tech

  • 主页
  • 随笔
  • 关于我

docker container network connect with host and another local area network host with macvlan

阅读数:次 2025-01-11
字数统计: 2k字   |   阅读时长≈ 9分

基于 Docker 的 Macvlan 网络容器管理完整解决方案

一、背景与目标

在分布式系统、实验平台等场景中,往往需要动态批量创建容器,并要求容器能与局域网服务器、宿主机实现双向通信。为了满足该需求,我们采用 Docker Macvlan 网络方案,结合两种容器批量创建方法(Docker Compose 和 Docker SDK for Python / docker-py),实现灵活的容器管理平台。


二、网络方案设计

2.1 需求回顾

  • 宿主机 IP:172.21.2.6
  • 容器 IP 范围:172.21.2.100-172.21.2.200
  • 网关:172.21.2.1
  • 容器、宿主机、局域网其他设备互通

Docker Networking Principles:

  • Bridge Network: Default local communication on the same Docker host.
  • Host Network: Container shares host’s network namespace.
  • Overlay Network: Cross-host communication in Swarm or Kubernetes.
  • Macvlan Network: Containers appear as physical devices on the network.
  • ipvlan: Give users total control over both IPv4 and IPv6 addressing.

根据需求,测试了ipvlan和macvlan,最终只有macvlan通过测试。

2.2 Macvlan 网络创建命令

让容器直接接入局域网,获取局域网 IP,变成一个局域网中的“独立设备”。

1
2
3
4
5
docker network create -d macvlan \
--subnet=172.21.2.0/24 \
--gateway=172.21.2.1 \
-o parent=enp61s0f0 \
my-macvlan

enp61s0f0: host physical network card

2.3 宿主机 macvlan 子接口配置

macvlan方案的缺点:

  • 容器与宿主机在 macvlan 网络内 不能直接通信,原因是 macvlan 会把容器和宿主机隔离,宿主机的网卡无法接收自己发出的 macvlan 流量。
  • 解决方案:给宿主机也创建 macvlan 子接口参与网络。
1
2
3
ip link add enp61s0f0-macvlan0 link enp61s0f0 type macvlan mode bridge
ip addr add 172.21.2.6/24 dev enp61s0f0-macvlan0
ip link set enp61s0f0-macvlan0 up

三、两种容器批量管理方法

3.1 方法一:Docker Compose + 动态 YAML 文件

实现原理

  • 根据实验需求生成动态 docker-compose.yaml 文件
  • 通过 docker-compose up -d 一键批量启动容器

示例 YAML 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3'
services:
container_1:
image: alpine:latest
container_name: container_1
networks:
my-macvlan:
ipv4_address: 172.21.2.100
restart: always
command: ["sh", "-c", "tail -f /dev/null"]
networks:
my-macvlan:
external: true

动态生成 + 启动示例代码

Python 脚本生成 YAML 后执行 docker-compose:

1
os.system(f'docker-compose -f {yaml_file_path} up -d')

优缺点

优点 缺点
简单、适合批量固定部署 需要生成 YAML 文件,需定期清理
支持 docker-compose 的所有功能 容器管理需依赖 YAML 文件

3.2 方法二:Docker SDK for Python (docker-py) 动态创建

实现原理

  • 通过 Python 脚本动态管理容器
  • 自动检测创建 macvlan 网络和子接口
  • 自动分配未占用 IP

核心示例代码

1
2
3
4
5
6
7
8
9
10
11
12
def create_container(ip, name, command):
client.containers.run(
image='alpine:latest',
name=name,
command=command or ["sh", "-c", "tail -f /dev/null"],
network='my-macvlan',
hostname=name,
detach=True,
restart_policy={"Name": "always"},
mac_address=None,
labels={'experiment': 'exp1'}
)

完整自动化功能

  • 自动检测并创建 macvlan 网络
  • 自动检测并创建 macvlan 子接口
  • 动态 IP 分配(避免冲突)
  • 容器批量创建,自动命名
  • 支持自定义 command

优缺点

优点 缺点
无需 YAML 文件,纯代码管理灵活 相对复杂,需编程实现
动态 IP 分配更智能 需要手动处理异常及容器清理

四、两种方法的比较与选型建议

项目 Docker Compose Docker SDK (docker-py)
管理方式 静态 YAML 文件 全动态 API
适合场景 固定批量容器 灵活动态批量容器
网络配置 需手动创建 自动检测创建
IP 分配 静态指定 动态分配未占用 IP
文件管理 需要管理 YAML 无文件冗余
扩展性 一般 高

推荐

  • 固定模式、快速复用场景:Docker Compose
  • 高动态、需智能分配场景:Docker SDK (docker-py)

五、未来优化方向

  • 容器生命周期自动管理(如超时自动关闭)
  • 容器监控平台集成
  • 容器日志统一管理
  • 支持多宿主机联合管理

六、总结

本文通过 Docker Macvlan 网络方案实现容器与局域网互通,并提供 Docker Compose 与 Docker SDK 两种容器批量管理方式。根据实际需求灵活选择,既保障了灵活性,也提升了管理效率。


七、代码附录

docker-compose

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 动态生成 docker-compose.yml 并执行
import os
import yaml
import subprocess
from typing import List

# 默认的网络配置,提前docker network创建并将宿主机也加入这个网络
NETWORK_NAME = "my-macvlan"
# 容器ip网段
SUBNET_PREFIX = "172.21.2."
# 容器ip起始
IP_RANGE_START = 100
# 容器ip终止
IP_RANGE_END = 200

# 检查IP是否可用
def is_ip_free(ip: str) -> bool:
result = subprocess.run(
["ping", "-c", "1", ip],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
return result.returncode != 0 # 0表示IP已被占用,非0表示可用

# 生成YAML并启动容器
def generate_yaml_and_start(experiment_id: str, image: str, count: int, command: List[str] = ["sh", "-c", "tail -f /dev/null"]):
project_dir = f"/opt/experiments/{experiment_id}"
os.makedirs(project_dir, exist_ok=True)

services = {}
ip_index = IP_RANGE_START
for i in range(count):
while ip_index <= IP_RANGE_END:
ip = SUBNET_PREFIX + str(ip_index)
if is_ip_free(ip):
break
ip_index += 1
else:
raise Exception("No available IPs")

services[f"{experiment_id}_{i}"] = {
"image": image,
"container_name": f"{experiment_id}_{i}",
"networks": {
NETWORK_NAME: {
"ipv4_address": ip
}
},
"restart": "always",
"command": command
}
ip_index += 1

compose_data = {
"version": "3",
"services": services,
"networks": {
NETWORK_NAME: {
"external": True
}
}
}

yaml_file = os.path.join(project_dir, "docker-compose.yml")
with open(yaml_file, "w") as f:
yaml.dump(compose_data, f)

# 启动容器
subprocess.run(["docker-compose", "-f", yaml_file, "up", "-d"], check=True)
return f"{count} containers started."


if __name__ == "__main__":
experiment_id = "id_test"
image = "alpine:latest"
count = 3

# 必须有command启动一个进程,不然alpine容器无法启动
# 使用默认 command
result_default_command = generate_yaml_and_start(experiment_id, image, count)
print(f"Started containers with default command: {result_default_command}")

# 使用自定义的 command;到时候可以做上线操作
# custom_command = ["python", "-m", "http.server", "8080"] # 比如启动一个简单的 HTTP 服务
# result_custom_command = generate_yaml_and_start(experiment_id, image, count, command=custom_command)
# print(f"Started containers with custom command: {result_custom_command}")

docker-py

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# docker-py 实现
import docker
import subprocess
import os
import json
from docker.types import EndpointConfig, IPAMPool, IPAMConfig

# 配置
## docker网络driver模式
NETWORK_DRIVER="macvlan"
## docker网络名称
NETWORK_NAME = "my-macvlan"
## 宿主机有线网卡
PARENT_INTERFACE = "enp61s0f0"
## 宿主机分配macvlan的ip,局域网未占用
HOST_MACVLAN_IP="172.21.2.110/24"
SUB_INTERFACE = "macvlan-shim"
SUBNET = "172.21.2.0/24"
GATEWAY = "172.21.2.1"
## ip 网段前缀
IP_PREFIX="172.21.2."
## ip 范围
IP_RANGE = (100, 200)
IMAGE = "alpine:latest"
# 默认给alpine的指令,起进程,否则无法启动
COMMAND_DEFAULT = ["sh", "-c", "tail -f /dev/null"]

client = docker.from_env()


def run_command(cmd):
result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout.decode(), result.stderr.decode()


# 自动检测并创建宿主机 macvlan 子接口
def ensure_macvlan_interface():
output, _ = run_command(f"ip link show {SUB_INTERFACE}")
if SUB_INTERFACE not in output:
run_command(f"ip link add {SUB_INTERFACE} link {PARENT_INTERFACE} type macvlan mode bridge")
run_command(f"ip addr add {HOST_MACVLAN_IP} dev {SUB_INTERFACE}")
run_command(f"ip link set {SUB_INTERFACE} up")


# 自动检测并创建 macvlan 网络
def ensure_macvlan_network():
networks = client.networks.list(names=[NETWORK_NAME])
if not networks:
ipam_pool = IPAMPool(subnet=SUBNET, gateway=GATEWAY)
ipam_config = IPAMConfig(pool_configs=[ipam_pool])
client.networks.create(
name=NETWORK_NAME,
driver=NETWORK_DRIVER,
options={"parent": PARENT_INTERFACE},
ipam=ipam_config
)


def find_unused_ip():
used_ips = set()
for container in client.containers.list():
try:
ip = container.attrs['NetworkSettings']['Networks'][NETWORK_NAME]['IPAddress']
used_ips.add(ip)
except KeyError:
continue
for i in range(IP_RANGE[0], IP_RANGE[1] + 1):
ip = f"{IP_PREFIX}{i}"
if ip not in used_ips:
return ip
raise Exception("No available IPs")


def create_containers(experiment_id, num_containers, command=COMMAND_DEFAULT):
ensure_macvlan_interface()
ensure_macvlan_network()

for i in range(num_containers):
container_name = f"{experiment_id}_container_{i + 1}"
ip = find_unused_ip()
endpoint_config = client.api.create_endpoint_config(
ipv4_address=ip
)
networking_config = client.api.create_networking_config({
NETWORK_NAME: endpoint_config
})
client.api.create_container(
name=container_name,
image=IMAGE,
command=command,
host_config=client.api.create_host_config(restart_policy={"Name": "always"}),
networking_config=networking_config
)
client.api.start(container_name)
print(f"Started container {container_name} with IP {ip}")


def remove_containers(experiment_id):
for container in client.containers.list(all=True):
if experiment_id in container.name:
container.stop()
container.remove()
print(f"Removed container {container.name}")


if __name__ == "__main__":
EXPERIMENT_ID = "exp_id0307"
NUM_CONTAINERS = 3
COMMAND = ["sh", "-c", "echo Hello World && tail -f /dev/null"]

print("Creating containers...")
create_containers(EXPERIMENT_ID, NUM_CONTAINERS, COMMAND)

# 测试删除
# print("Removing containers...")
# remove_containers(EXPERIMENT_ID)
  • 本文作者: Linking
  • 本文链接: https://linking.fun/2025/01/11/docker-container-network-connect-with-host-and-another-local-area-network-host-with-macvlan/
  • 版权声明: 版权所有,转载请注明出处!
  • network
  • container
  • macvlan
  • docker

扫一扫,分享到微信

Ubuntu 22.04 服务器网络问题定位与解决
running-records
  1. 1. 基于 Docker 的 Macvlan 网络容器管理完整解决方案
    1. 1.1. 一、背景与目标
    2. 1.2. 二、网络方案设计
      1. 1.2.1. 2.1 需求回顾
      2. 1.2.2. 2.2 Macvlan 网络创建命令
      3. 1.2.3. 2.3 宿主机 macvlan 子接口配置
    3. 1.3. 三、两种容器批量管理方法
      1. 1.3.1. 3.1 方法一:Docker Compose + 动态 YAML 文件
        1. 1.3.1.1. 实现原理
        2. 1.3.1.2. 示例 YAML 模板
        3. 1.3.1.3. 动态生成 + 启动示例代码
        4. 1.3.1.4. 优缺点
      2. 1.3.2. 3.2 方法二:Docker SDK for Python (docker-py) 动态创建
        1. 1.3.2.1. 实现原理
        2. 1.3.2.2. 核心示例代码
        3. 1.3.2.3. 完整自动化功能
        4. 1.3.2.4. 优缺点
    4. 1.4. 四、两种方法的比较与选型建议
      1. 1.4.1. 推荐
    5. 1.5. 五、未来优化方向
    6. 1.6. 六、总结
    7. 1.7. 七、代码附录
      1. 1.7.1. docker-compose
      2. 1.7.2. docker-py
© 2015-2026 Linking
GitHub:hexo-theme-yilia-plus by Litten
本站总访问量次 | 本站访客数人
  • 所有文章
  • 外链

tag:

  • weather
  • 需求
  • essay
  • basketball
  • olympic
  • nginx
  • APPScan
  • SQl盲注
  • xss
  • Ajax
  • ajax
  • ai
  • agent
  • openclaw
  • ccf
  • Nginx
  • HTML5
  • html5
  • hmtl5
  • sse
  • JavaScriptCore
  • Oracle
  • operation
  • Linux
  • deploy
  • Mac Office
  • markdown
  • ListView
  • GridView
  • MySQL
  • 慢查询
  • mongodb
  • 转置
  • thought
  • network
  • ubuntu
  • NetworkManager
  • RFKill
  • Netplan
  • avatar
  • cocoa
  • blog
  • Gitalk
  • container
  • macvlan
  • docker
  • oracle
  • cookie
  • patch
  • gitea
  • git
  • iOS
  • https
  • 多线程
  • bundle
  • 兼容性
  • HTTP
  • 绘图
  • cs
  • java
  • 效率
  • 快捷键
  • route
  • nodejs
  • pip
  • arcgis
  • arcgis 建模
  • 标识
  • redis
  • read
  • bookList
  • running
  • showdoc
  • disk
  • unit-test
  • D.Wade
  • thoughts
  • duoduo
  • Python
  • python
  • tomcat
  • 读书节
  • session
  • jdk
  • war
  • 加班
  • Android onclick事件监听
  • 正则
  • 手机品牌匹配
  • ntp
  • OpenLayers
  • Geoserver
  • wechat
  • 微信公众号
  • 爬虫
  • WeChat
  • 张靓颖
  • 动漫
  • vpn
  • PPT
  • MarkDown
  • plan
  • 朱赟
  • 极客时间专栏
  • 极客邦
  • 模块化
  • MVC
  • excel
  • NBA
  • kobe
  • team
  • crawler
  • 进度条
  • ssl
  • book
  • anti-stealing-link
  • Agentic Engineering
  • Vibe Coding
  • Software 3.0
  • Andrej Karpathy
  • LLM
  • Programming

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia-plus根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • GitHub Trending
  • OpenAI ChatGPT
  • Gitee码云
  • 简书
  • CSDN