一、概念介绍
1.1 什么是macvlan
macvlan是一种网卡虚拟化技术,能够将一张网卡(Network Interface Card, NIC)虚拟出多张网卡,这意味着每个虚拟网卡都能拥有独立的MAC地址和IP地址,从而在系统层面表现为完全独立的网络接口。这些虚拟网卡可以直接连接到物理网络,就像是网络中单独的物理设备一样,而不像传统的网络桥接或VLAN那样需要一个共同的桥接接口来转发流量。可以简单理解为macvlan是docker容器中的一种网络模式,可以让容器直接连接到宿主机的物理网络。
1.2 什么是docker容器
Docker容器是一种轻量级、可移植的软件封装技术,它允许开发者将应用程序及其所有的依赖包打包到一个独立的、可运行的包中。这个包可以在任何安装了Docker引擎的系统上运行,无论该系统是在开发、测试还是生产环境中,保证了应用运行环境的一致性。Docker容器利用操作系统的Namespace和Control Groups等技术,实现了资源的隔离和限制,使得每个容器都像是运行在一个独立的系统中,但实际共享着宿主机的内核。
1.3docker容器与虚拟机对比
- 资源消耗:虚拟机运行在宿主机上的虚拟化层(如Hypervisor)上,每个虚拟机都需要自己的操作系统,这导致较高的资源开销。相比之下,容器直接运行在宿主机的操作系统上,共享宿主机的内核,因此资源消耗小得多。
- 启动速度:容器由于不需要启动完整的操作系统,其启动速度远远快于虚拟机。
- 隔离程度:虚拟机提供了更强的隔离性,每个虚拟机都有自己的硬件虚拟化层,包括CPU、内存、硬盘等,而容器共享宿主机的硬件资源,隔离性相对较弱,但通过命名空间和控制组仍能有效隔离。
- 灵活性与便携性:容器镜像体积小,便于在网络上分发和快速部署,更适合动态扩缩容和快速迭代的开发模式。
- 适用场景:虚拟机更适合需要高度隔离或需要运行完全不同操作系统环境的场景,而容器则更适合微服务架构、快速部署、轻量级隔离的场景。
1.4 docker的五种网络模式
Bridge (桥接模式) ---默认模式
解释:Bridge模式是Docker的默认网络配置。每个使用此模式的容器都会得到一个独立的Network Namespace,Docker会为容器分配一个内部IP地址,并将其连接到一个名为docker0的虚拟网桥上。这意味着容器间可以相互通信,同时也能够通过宿主机的网络栈访问外部网络。
适用情况:当你需要容器之间能够直接通信,且容器需要访问互联网或者被外部网络访问时,使用Bridge模式最为常见。
Host (主机模式)
解释:在Host模式下,容器不会获得自己的Network Namespace,而是直接使用宿主机的网络堆栈。这意味着容器将共享宿主机的网络接口和端口,容器内的网络服务可以直接使用宿主机的IP地址和端口号对外提供服务。
适用情况:如果你的应用需要直接绑定到宿主机的网络接口,或者需要极低的网络延迟,可以考虑使用Host模式。但需要注意,这样做可能会导致安全风险和端口冲突问题。
None (无网络模式)
解释:None模式下,Docker不会为容器配置任何网络设施,容器将只有一个lo(loopback)接口,无法访问外部网络,也无法和其他容器通信,除非你手动配置网络。
适用情况:如果你的容器不需要网络连接,或者你计划完全自定义网络配置(例如,使用自定义网络接口或复杂的网络拓扑),可以选择None模式。
Container (容器模式)
解释:这种模式允许一个容器共享另一个容器的Network Namespace,而不是创建新的或使用宿主机的。这意味着两个容器将共享相同的网络配置,包括IP地址和端口空间。
适用情况:当多个容器需要共享相同的网络环境时,比如在Kubernetes中的Pods,所有容器需要共享同一套网络资源和配置时,这种模式非常有用。
Macvlan (MAC VLAN 模式)
Macvlan 是一种网络虚拟化技术,允许在单个物理网络接口上创建多个虚拟以太网接口(每个都有独立的MAC地址),这些虚拟接口可以直接连接到物理网络,仿佛是直接连接到交换机上的独立物理设备。在 Docker 中使用 macvlan 模式,可以为每个容器提供一个直接与物理网络相连的网络接口,绕过 Docker 默认的网络桥接,从而获得更低的网络延迟和更接近物理机的网络行为。这种模式非常适合需要直接与外部网络交互,且要求低延迟或特定网络配置的场景。
二、 Docker的安装与使用
2.1 Docker的安装
安装教程:https://docker-practice.github.io/zh-cn/install/ubuntu.html
安装完后一定要添加镜像源:
比如我在广州或东莞:
{
"registry-mirrors": ["https://cn-guangzhou.mirror.aliyuncs.com"]
}
基础操作:
nano daemon.json #进入文件,文件不存在时自动创建
ctrl + o 保存文件 ctrl + x 退出文件 rm -i 文件名 #删除文件
mkdir 目录名 #创建目录 rmdir 目录名 #删除目录
更改文件前建议备份文件:
- cp example.txt example.txt.backup #假设要编辑的文件名为
example.txt,cp命令创建一个副本
- mv example.txt.backup example.txt #如果修改后的文件已经保存,你可以直接用mv将备份文件覆盖回去
- diff example.txt.example.txt.backup #如果你不确定是否需要完全恢复,或者想查看具体哪些地方发生了变化,可以使用
diff
命令比较两个文件的差异,然后手动决定如何修改。
安装完上面后再拉取镜像
docker pull ubuntu #获取Ubuntu的最新长期支持版本(LTS)
docker pull ubuntu:20.04 #如果你需要特定版本,比如10.04
2.1.1 相关知识了解
daemon.json
是Docker守护进程(dockerd
)的配置文件,它允许系统管理员自定义Docker守护程序的行为和参数。这个文件通常位于/etc/docker/
目录下,并且在Docker Engine 1.12及更高版本中可以被使用。docker服务使用 systemctl start docker 命令启动失败时,考虑daemon.json的配置是否正确。作用:
网络配置:通过
daemon.json
,你可以配置Docker的网络设置,比如桥接网络的子网、网关等。日志记录:配置Docker日志的驱动、日志级别以及日志输出目标,比如将日志发送到syslog或文件系统。
存储驱动:选择或修改Docker使用的存储驱动(如overlay2、aufs等),这对于容器的存储性能和管理非常重要。
镜像加速:对于中国用户而言,可以在该文件中配置镜像加速器地址,以加快从Docker Hub拉取镜像的速度。
注册表镜像认证:配置私有注册表的认证信息,使得Docker守护进程能够无需交互式输入凭据就能拉取私有镜像。
TLS配置:设定Docker守护进程的TLS加密和证书路径,增强Docker API的安全性。
资源限制:例如内存、CPU使用限制,可以在这里全局设置Docker容器可使用的最大资源量。
live-restore:设置容器在Docker守护进程重启时不退出,保持容器持续运行。
其他高级选项:包括IPv6支持、容器默认的隔离技术、容器标签策略等。
2.2 Docker的基础使用命令
查看信息 --------------------------------------------------------------------------
docker ps # 查看正在运行的容器
docker ps -a # 查看所有容器
#查看容器相关信息(推荐使用这个)
#打印网络名称、容器名称、主机名及对应的IP地址
docker inspect --format '{{range $key, $value := .NetworkSettings.Networks}}{{$key}}: {{$.Name}} - {{$.Config.Hostname}} - {{$value.IPAddress}}{{println}}{{end}}' 容器名/ID
#查看存在的网络
docker network ls
#查看某个网络下连接的容器
docker network inspect 网络名或ID
#查看容器连接的网络
docker inspect 容器名/ID
ip addr show #列出系统中所有网络接口,包括IP地址、子网掩码、广播地址、网络接口的状态
ip route #展示系统的路由表。路由表是决定数据包在网络中如何转发
删除 ------------------------------------------------------------------------------
#将容器从指定网络断开
docker network disconnect my_network my_container
#删除网络(无容器连接时才可删除)
docker network rm my_network -f (-f表示强制删除)
#删除容器
docker rm 容器名/ID #(正在运行容器不能删除,除非加-f选项)
进入和退出容器 ------------------------------------------------------------------------
#进入容器前要先启动
docker start 容器名/ID #启动容器 无法启动时检查/etc/docker/daemon.json文件
docker restart 容器名/ID #重启容器
docker stop 容器名/ID #停止正在运行的容器
# 进入容器,推荐exec 进入容器前要先启动容器
docker attach 容器名/ID #使用attach进入后退出,容器停止运行
docker exec -it 容器名/ID /bin/bash #使用exec进入后退出,容器不会停止
docker exec -it 容器名/ID bash
# 退出容器
exit #直接退出
crlt + P 再按 ctrl + Q #退出容器但是不终止运行
docker相关服务 ------------------------------------------------------------------------
systemctl start docker #启动服务
systemctl status docker #查看状态 active(runing)表示已启动
systemctl stop docker #停止服务
ubuntu防火墙 ---------------------------------------------------------------------------
sudo ufw status #查看防火墙状态,inactive是关闭,active是开启
sudo ufw enable #开启防火墙
udo ufw disable #关闭防火墙
三、创建macvlan网络连接的容器
3.1 创建macvlan网络
创建容器时必须选择连接一个网络,如果不选择就是默认桥接模式brideg,可以使用docker network ls查看目前存在的网络。
sudo docker network create \
--driver macvlan \
--subnet=<你的宿主机子网,如192.168.0.0/24> \
--gateway=<你的宿主机网关> \
-o parent=物理接口名称,如ens33 \
net-1
参数介绍:
- --driver macvlan:指定网络类型为macvlan。
- --subnet:
192.168.1.0/24
表示容器将会从这个子网范围内分配IP地址- --gateway: 通过宿主机网关容器知道如何路由到外部网络
- -o parent: 指定macvlan网络将绑定到宿主机的哪个物理网络接口
- net-1: 这个是你创建的网络名称
3.2 查询创建网络时需要的相关信息
docker network ls #显示所有网络,如net-1
ip addr #查看所有网络接口的详细信息 ifconfig也可以
查询信息如下列所示:
我的物理网络接口为ens33,所以接口的IP地址配置显示为
192.168.1.100/24
。这里的/24
就是子网掩码的CIDR表示形式,意味着网络部分有24个二进制的1,即子网掩码为255.255.255.0
。因此,从这个输出中可以看出,该接口配置在一个子网为192.168.1.0/24
的网络中,一般来说默认网关就是你的ip地址,比如这里的192.168.1.100。如果不确定则按下面步骤查询。
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
inet 192.168.1.100/24 brd 192.168.1.255 scope global dynamic ens33
valid_lft 3444sec preferred_lft 3444sec
ip route #查看路由表,网关,其中包括了目标网络、网关、接口、标志以及路由的优先级等信息。路由表来决定数据包应当通过哪个接口(网络接口卡,如 Ethernet 卡)以及下一跳的IP地址(通常是网关)来转发数据。
default via [网关IP] dev [网络接口名称] proto [协议] metric [数值]
- [网关IP]:这部分显示的就是你的默认网关地址。
- [网络接口名称]:表示数据包将通过哪个网络接口发送到默认网关。
- [协议]:可能是dhcp, static, kernel, boot等,表示路由设置的来源。
- [数值]:metric值,用于衡量路径的优先级,数值越小优先级越高。
3.3 创建一个docker容器
sudo docker run -itd \
--name container-1 \
--network=net-1 \
--ip=192.168.0.61 \
-v /home/norten/Public/tools:/mnt \
ubuntu
参数介绍:
- -i: 表示以交互模式运行容器,即使没有连接到终端也会保持STDIN(标准输入)打开
- -t: 为容器分配一个伪TTY(虚拟终端),这使得它看起来更像是一个交互式会话
- -d: 表示以后台模式运行容器(守护进程模式),你不会直接看到容器的输出,但容器会在后台持续运行
- --name container-1: 为新创建的容器指定一个名称
- --network=net-1: 指定容器要加入的网络名为
net-1
- --ip=192.168.0.61: 为容器指定了一个固定的IP地址
- -v /home/norten/Public/tools:/mnt: 使用卷挂载功能,将宿主机的
/home/norten/Public/tools
目录挂载到容器内的/mnt
目录,mnt全称为mount挂载- ubuntu: 这是告诉Docker使用
ubuntu
镜像作为基础来创建容器
需要注意的是:
- 容器要先使用docker start [容器名称] 命令启动后才能够进入容器内部
- 可以使用dokcer ps查看所有正在运行的容器,docker ps -a显示所有存在的容器
- 使用docker exec -it [容器名称] bash 进入容器后,使用exit退出时容器仍正常运行
- 使用docker attach [容器名称] 进入容器后,使用exit退出容器则容器停止运行
- 可以使用docker inspect [网络名称] 查看该网络所有相关的信息
- 可以使用docker inspect [容器名称] 查看该容器所有相关的信息,如以下图片所示
docker inspect container-1
Mounts: (对应mnt)
Networks:
同样的,我们可以使用 docker inspect net-1 来查看这个网络上所连接的容器
(这里展示的是部分容器连接)
上面我们是使用了ssh终端(MobaXterm)来实现与ubuntu宿主机的交互,在宿主机上使用卷挂载来实现宿主机与容器之间的交互。还有一种方式可以实现windows电脑与ubuntu宿主机之间共享文件,具体看:
- ubuntu访问windows共享文件夹-CSDN博客
- 在ubuntu中创建容器并挂载windows共享的文件(SMB挂载到本地后,本地的文件再挂载到容器中)-CSDN博客
除此以外,容器在创建的时候还可以指定DNS,先查询宿主机的DNS,再指定容器的DNS,则能够让容器与宿主机DNS一致,是否需要看个人用途,这个不作过多介绍
sudo docker run -itd \ --name container-5 \ --network=my-macvlan-1 \ --ip=192.168.0.65 \ -v /home/norten/Desktop/SmbShare:/home/public/tools/MediumBoxBase \ --dns 宿主机DNS \ --dns 8.8.8.8 \ --dns 114.114.114.114 \ --dns-opt="ndots:0" \ --dns-opt="edns0" \ ubuntu
3.4 docker容器的使用
进入容器后直接使用脚本:./mnt/container_run_medium.sh
container_run_medium.sh内的脚本内容:
rm -f *.log
rm -f nohup.out
rm -f cssd.dat
nohup /mnt/simutools/pwbox_simu /mnt/simutools/pw_box.conf &
/mnt/mediumSimu/MediumBoxBase /mnt/mediumSimu/hynn_flash_config_simu.conf
- 查看当前容器ID
cat /etc/hostname
- 查看当前所在容器名称:
docker inspect --format '{{.Name}}' 容器ID
脚本docker_operations.sh:
#!/bin/bash
# all definition
NETWORK_NAME="net-1"
VOLUME_MOUNT="-v /home/norten/Public/tools:/mnt"
IMAGE_NAME="ubuntu"
# View help command
function help_container() {
echo "/mnt/simutools# ./pwbox_simu pw_box.conf "
echo "/mnt/mediumSimu# ./MediumBoxBase hynn_flash_config_simu.conf "
echo " "
echo " "
echo "create: ./docker_operations.sh create [num]" #创建一个容器
echo "./docker_operations.sh create 20 *means* create container-20 ,ip=192.168.0.80"
echo " "
echo " "
echo "start: ./docker_operations.sh start [start_num] [end_num]" #启动容器
echo "./docker_operations.sh start 20 25 *means* start 20-25 containers"
echo "./docker_operations.sh start 20 *means* start one container"
echo " "
echo " "
echo "exec: ./docker_operations.sh exec [num] " #进入容器
echo "./docker_operations.sh exec 20 *means* execute container-20"
echo "After you execute container ,you should use <exit> to exit your container"
echo " "
echo " "
echo "stop: ./docker_operations.sh stop [start_num] [end_num]" #停止容器
echo "./docker_operations.sh stop 20 25 *means* stop 20-25 containers"
echo "./docker_operations.sh stop 20 *means* stop one container"
echo " "
echo " "
echo "remove: ./docker_operations.sh remove [num] " #删除容器
echo "./docker_operations.sh remove 20 *means* remove container-20"
echo " "
echo " "
echo "info: ./docker_operations.sh info [container_ID] "
echo " ./docker_operations.sh info a536fbad17b4 *means* view the container name" #进入容器后输入命令左边那一串就是容器的ID
echo " "
echo " "
echo "<docker ps>" #查看所有的容器
echo "Used to see which containers are running"
echo " "
echo " "
echo "<docker ps -a>" #查看正在运行的容器
echo "Used to see all containers exist"
echo " "
echo " "
echo "<docker inspect container-[num]>"
echo "Used to check all information about a container"
echo " "
echo " "
}
# Dynamic container creation
function create_container() {
echo "create zero paremeter is: $0"
echo "create first paremeter is: $1"
echo "create second paremeter is: $2"
local num="$1"
local CONTAINER_IP="192.168.0.$((num+60))"
echo "IP IS $CONTAINER_IP"
local CONTAINER_NAME="container-$num"
# Check whether the IP address is already in use
local existing_ips=($(docker inspect --format='{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aq) 2>/dev/null))
for ip in "${existing_ips[@]}"; do
if [[ "$ip" == "$CONTAINER_IP" ]]; then
echo "Error: IP Address $CONTAINER_IP is already in use by another container."
exit 1
fi
done
# Trying to create a container
docker run -itd \
--name "$CONTAINER_NAME" \
--network="$NETWORK_NAME" \
--ip="$CONTAINER_IP" \
$VOLUME_MOUNT \
$IMAGE_NAME \
&& echo "Container $CONTAINER_NAME created with IP $CONTAINER_IP." \
|| { echo "Failed to create container $CONTAINER_NAME."; exit 1; }
}
# Start specified or a range of containers
function start_container() {
echo "start zero paremeter is: $0"
echo "start first paremeter is: $1"
echo "start second paremeter is: $2"
local start_num="$1"
local end_num="${2:-$start_num}" # If the second argument is not provided, it defaults to the value of the first argument
for (( i=start_num; i<=end_num; i++ )); do
local CONTAINER_NAME="container-$i"
if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME\$"; then
echo "Starting container $CONTAINER_NAME..."
docker start "$CONTAINER_NAME"
echo "Container $CONTAINER_NAME started."
else
echo "Error: Container $CONTAINER_NAME does not exist."
exit 1
fi
done
}
# Stop specified or a range of containers
function stop_container() {
local start_num="$1"
local end_num="${2:-$start_num}" # If the second argument is not provided, it defaults to the value of the first argument
for (( i=start_num; i<=end_num; i++ )); do
local CONTAINER_NAME="container-$i"
if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME\$"; then
echo "Stopping container $CONTAINER_NAME..."
docker stop "$CONTAINER_NAME"
echo "Container $CONTAINER_NAME stopped."
else
echo "Warning: Container $CONTAINER_NAME does not exist."
fi
done
}
# Enter the shell of a specified container
function exec_container() {
local container_num="$1"
local CONTAINER_NAME="container-$container_num"
if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME\$"; then
echo "Entering container $CONTAINER_NAME..."
docker exec -it "$CONTAINER_NAME" bash
else
echo "Error: Container $CONTAINER_NAME does not exist or is not running."
exit 1
fi
}
# Remove a specified container
function remove_container() {
local container_num="$1"
local CONTAINER_NAME="container-$container_num"
if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME\$"; then
echo "Removing container $CONTAINER_NAME..."
docker rm -f "$CONTAINER_NAME"
echo "Container $CONTAINER_NAME removed."
else
echo "Error: Container $CONTAINER_NAME does not exist."
exit 1
fi
}
# 添加一个新的函数来查询容器信息
function info_container() {
local search_term="$1"
local containers_info=$(docker ps -a --format '{{.ID}} {{.Names}}' | grep "$search_term")
if [ -z "$containers_info" ]; then
echo "No container found matching '$search_term'."
return 1
fi
echo "Matching containers:"
echo "$containers_info" | while read -r container_id container_name; do
echo "ID: $container_id, Name: $container_name"
done
}
case "$1" in
help)
help_container
;;
create)
if [ "$#" -ne 2 ]; then
echo "Error: You should provide a parameter after 'create'."
exit 1
fi
if ! [[ "$2" =~ ^[1-9][0-9]{0,2}|[1-9][0-9]{3}$ ]]; then
echo "Error: The number must be an integer between 1 and 1000."
exit 1
fi
create_container "$2"
;;
start)
# Check the number of parameters to determine whether to start a single container or a container range
if [ "$#" -lt 2 ]; then
echo "Error: You should provide at least one number after 'start'."
exit 1
elif [ "$#" -eq 2 ]; then
# If there are only two parameters, try starting a container
start_container "$2"
elif [ "$#" -eq 3 ]; then
# If you have three parameters, try starting a series of containers
if ! [[ "$2" =~ ^[1-9][0-9]{0,2}|[1-9][0-9]{3}$ ]] || ! [[ "$3" =~ ^[1-9][0-9]{0,2}|[1-9][0-9]{3}$ ]]; then
echo "Error: Both numbers must be integers between 1 and 1000."
exit 1
fi
if [ "$2" -gt "$3" ]; then
echo "Error: The first number must be less than or equal to the second."
exit 1
fi
start_container "$2" "$3"
else
echo "Error: Too many arguments for 'start'."
exit 1
fi
;;
stop)
if [ "$#" -lt 2 ]; then
echo "Error: You should provide at least one number after 'stop'."
exit 1
fi
if ! [[ "$2" =~ ^[1-9][0-9]{0,2}|[1-9][0-9]{3}$ ]]; then
echo "Error: The number(s) must be integers between 1 and 1000."
exit 1
fi
if [ "$#" -eq 2 ]; then
stop_container "$2"
elif [ "$#" -eq 3 ]; then
if ! [[ "$3" =~ ^[1-9][0-9]{0,2}|[1-9][0-9]{3}$ ]]; then
echo "Error: Both numbers must be integers between 1 and 1000."
exit 1
fi
if [ "$2" -gt "$3" ]; then
echo "Error: The second number must be greater than or equal to the first."
exit 1
fi
stop_container "$2" "$3"
else
echo "Error: Too many arguments for 'stop'."
exit 1
fi
;;
exec)
if [ "$#" -ne 2 ]; then
echo "Error: You should provide exactly one number after 'exec'."
exit 1
fi
if ! [[ "$2" =~ ^[1-9][0-9]{0,2}|[1-9][0-9]{3}$ ]]; then
echo "Error: The number must be an integer between 1 and 1000."
exit 1
fi
exec_container "$2"
;;
remove)
if [ "$#" -ne 2 ]; then
echo "Error: You should provide exactly one number after 'remove'."
exit 1
fi
if ! [[ "$2" =~ ^[1-9][0-9]{0,2}|[1-9][0-9]{3}$ ]]; then
echo "Error: The number must be an integer between 1 and 1000."
exit 1
fi
remove_container "$2"
;;
info)
if [ "$#" -ne 2 ]; then
echo "Usage: $0 info <container_search_term>"
exit 1
fi
info_container "$2"
;;
logs|status)
echo "Function '$1' has not been updated to handle numbered containers."
exit 1
;;
*)
echo "Invalid command. Use './docker_operations.sh help' to get instructions."
exit 1
;;
esac
exit 0
四、shell脚本
vi [name].sh #编写
more [name].sh #查看信息
chmod +x [name].sh #添加权限
fuser -n file xxx.sh #查找占用进程
kill Process_ID