这篇文章详细介绍了Docker容器的基本概念、创建和管理方法。内容包括Docker容器的定义、容器与镜像的关系、如何使用Docker命令创建和启动容器,以及常用的容器管理命令。文章还讲解了如何进入容器、查看容器日志、停止和删除容器等操作。通过实例和命令行示例,帮助读者深入理解和掌握Docker容器的使用。整体内容适合初学者和有一定基础的用户参考学习。
什么是容器?
Docker 容器是一个轻量级、独立、可执行的软件包,它包含了运行某个应用程序所需的所有内容,包括代码、运行时、库、环境变量和配置文件。容器使用操作系统级虚拟化技术,在共享主机操作系统内核的基础上,实现了进程和资源的隔离。
- 轻量级:Docker 容器共享主机操作系统的内核,不需要像虚拟机那样运行一个完整的操作系统,因此启动速度快,占用资源少。
- 可移植性:Docker 容器可以在任何支持 Docker 的平台上运行,确保应用程序在不同环境中的一致性。
- 隔离性:每个容器都有自己的文件系统、进程空间和网络接口,与其他容器和主机系统隔离。
- 可重复性:通过 Docker 镜像,容器可以在不同的环境中重复创建,确保应用程序的可重复部署。
容器工作原理
Docker 容器基于以下几个关键技术:
- 命名空间(Namespaces):提供进程、网络、文件系统等资源的隔离。常见的命名空间包括 PID(进程 ID)、NET(网络)、IPC(进程间通信)、MNT(挂载点)、UTS(主机名)和 USER(用户)。
- 控制组(Cgroups):限制和隔离容器的资源使用,如 CPU、内存、I/O 等。Cgroups 允许对资源进行精细的控制和监控。
- 联合文件系统(UnionFS):支持分层镜像,使得镜像可以共享和复用,节省存储空间。常见的联合文件系统包括 AUFS、OverlayFS 和 Btrfs。
- 存储驱动:管理镜像和容器的存储,不同的存储驱动实现了不同的联合文件系统,如 AUFS、OverlayFS、Btrfs 等。
容器的硬件条件是基于宿主机的吗?
Docker 容器的性能是基于宿主机的硬件资源的。如果不设置任何资源限制,Docker 容器默认可以使用宿主机的所有可用资源。这意味着容器可以使用宿主机的全部 CPU、内存、存储和网络带宽,直到资源耗尽或受到其他系统限制。
默认资源配置
1. CPU
默认情况下,Docker 容器可以使用宿主机的所有 CPU 资源。容器中的进程与宿主机上的其他进程一样,参与 CPU 调度。
2. 内存
默认情况下,Docker 容器可以使用宿主机的所有可用内存。容器中的进程可以分配和使用内存,直到宿主机的内存耗尽。
3. 存储
默认情况下,Docker 容器可以使用宿主机的所有可用存储空间。容器的文件系统是临时的,存储在宿主机的 /var/lib/docker
目录中,除非使用了数据卷或绑定挂载。
4. 网络
默认情况下,Docker 容器可以使用宿主机的所有网络带宽。容器之间的网络通信通过 Docker 网络实现,默认是桥接网络(bridge)。
设置资源限制
为了防止某个容器过度使用资源,影响宿主机和其他容器的性能,可以使用 Docker 提供的资源限制选项来限制容器的 CPU、内存和 I/O 资源。
1. CPU 限制
限制 CPU 使用比例
可以使用 --cpu-shares
选项来设置容器的 CPU 共享权重。默认值是 1024,值越大,容器获得的 CPU 时间片越多。
docker run -d --name my-container --cpu-shares=512 nginx
限制 CPU 核心数
可以使用 --cpus
选项来限制容器可以使用的 CPU 核心数。
docker run -d --name my-container --cpus="1.5" nginx
这个命令限制容器最多使用 1.5 个 CPU 核心。
绑定到特定 CPU 核心
可以使用 --cpuset-cpus
选项来将容器绑定到特定的 CPU 核心。
docker run -d --name my-container --cpuset-cpus="0,1" nginx
这个命令将容器绑定到 CPU 0 和 CPU 1。
2. 内存限制
限制内存使用
可以使用 -m
或 --memory
选项来限制容器可以使用的内存。
docker run -d --name my-container -m 512m nginx
这个命令限制容器最多使用 512MB 内存。
限制交换内存使用
可以使用 --memory-swap
选项来限制容器可以使用的交换内存。
docker run -d --name my-container -m 512m --memory-swap=1g nginx
这个命令限制容器最多使用 512MB 内存和 512MB 交换内存,总共 1GB。
3. 块 I/O 限制
可以使用 --blkio-weight
选项来设置容器的块 I/O 权重。默认值是 500,值越大,容器获得的 I/O 带宽越多。
docker run -d --name my-container --blkio-weight=300 nginx
4. 网络带宽限制
限制容器的网络带宽通常需要使用额外的插件或工具,如 tc
(Linux 的流量控制工具)或 CNI 插件。
示例:限制容器的 CPU 和内存
以下是一个示例,演示如何限制容器的 CPU 和内存:
docker run -d --name my-container --cpus="1.5" -m 512m nginx
这个命令启动一个 Nginx 容器,并限制其最多使用 1.5 个 CPU 核心和 512MB 内存。
监控容器的资源使用
可以使用 docker stats
命令来监控容器的资源使用情况,包括 CPU、内存、网络和块 I/O。
docker stats my-container
容器间如何通信?容器网络
Docker 提供了几种不同的网络模式,每种模式适用于不同的使用场景。
1. Bridge 桥接(默认)
- 默认网络模式:当你不指定网络模式时,Docker 默认使用 Bridge 网络。
- 隔离性:每个容器连接到一个默认的桥接网络(
bridge
),该网络由 Docker 创建。 - 通信:容器之间可以通过 IP 地址或容器名称进行通信。容器可以通过主机的网络接口访问外部网络,但外部网络无法直接访问容器,除非通过端口映射。
使用场景
适用于大多数单机应用,特别是需要容器之间相互通信的场景。
示例
创建一个自定义的桥接网络,并运行两个容器连接到该网络:
# 创建自定义桥接网络docker network create my-bridge-network
# 运行第一个容器docker run -d --name container1 --network my-bridge-network nginx
# 运行第二个容器docker run -d --name container2 --network my-bridge-network busybox
在这个示例中,container1
和 container2
可以通过容器名称互相通信。
2. Host 网络
- 共享主机网络:容器共享主机的网络命名空间,容器的网络栈与主机相同。
- 高性能:由于没有网络虚拟化的开销,网络性能较高。
- 端口冲突:容器可以直接使用主机的 IP 地址和端口,可能会导致端口冲突问题。
使用场景
适用于需要高性能网络通信的场景,如需要低延迟的应用。
示例
运行一个容器并使用 Host 网络:
docker run -d --name my-container --network host nginx
在这个示例中,my-container
共享主机的网络命名空间,可以直接使用主机的 IP 地址和端口。
3. None 网络
- 完全隔离:容器没有网络接口,完全隔离。
- 无网络通信:容器无法与其他容器或外部网络通信。
使用场景
适用于不需要网络通信的容器,如只需要进行计算任务的容器。
示例
运行一个容器并使用 None 网络:
docker run -d --name my-container --network none nginx
在这个示例中,my-container
没有网络接口,无法与其他容器或外部网络通信。
4. Container 网络
- 共享网络命名空间:容器共享另一个容器的网络命名空间。
- 相同 IP 地址:共享网络命名空间的容器具有相同的 IP 地址和端口空间。
使用场景
适用于需要多个容器共享同一个网络栈的场景,如需要紧密协作的多容器应用。
示例
运行两个容器,第二个容器共享第一个容器的网络命名空间:
# 运行第一个容器docker run -d --name container1 nginx
# 运行第二个容器,共享第一个容器的网络命名空间docker run -d --name container2 --network container:container1 busybox
在这个示例中,container1
和 container2
共享相同的网络命名空间,具有相同的 IP 地址和端口空间。
5. Overlay 网络
- 跨主机通信:用于跨多个 Docker 主机的容器通信,通常用于 Docker Swarm 或 Kubernetes 集群。
- 虚拟网络:通过虚拟网络实现跨主机的容器通信。
使用场景
适用于分布式应用和集群环境,如 Docker Swarm 或 Kubernetes 集群。
示例
在 Docker Swarm 中创建一个 Overlay 网络,并运行服务连接到该网络:
# 初始化 Docker Swarmdocker swarm init
# 创建 Overlay 网络docker network create -d overlay my-overlay-network
# 运行服务连接到 Overlay 网络docker service create --name my-service --network my-overlay-network nginx
在这个示例中,my-service
运行在 Docker Swarm 集群中,并连接到 Overlay 网络 my-overlay-network
。
6. Macvlan 网络
- 直接连接物理网络:容器直接连接到主机的物理网络接口,具有独立的 MAC 地址和 IP 地址。
- 高性能:由于直接连接物理网络,网络性能较高。
使用场景
适用于需要容器直接与物理网络通信的场景,如需要与物理设备通信的应用。
示例
创建一个 Macvlan 网络,并运行容器连接到该网络:
# 创建 Macvlan 网络docker network create -d macvlan \ --subnet=192.168.1.0/24 \ --gateway=192.168.1.1 \ -o parent=eth0 my-macvlan-network
# 运行容器连接到 Macvlan 网络docker run -d --name my-container --network my-macvlan-network nginx
在这个示例中,my-container
直接连接到物理网络接口 eth0
,具有独立的 MAC 地址和 IP 地址。
总结
- Bridge 网络:默认网络模式,适用于大多数单机应用。
- Host 网络:共享主机网络,适用于高性能需求。
- None 网络:完全隔离,适用于不需要网络通信的容器。
- Container 网络:共享另一个容器的网络命名空间,适用于需要紧密协作的多容器应用。
- Overlay 网络:跨主机通信,适用于分布式应用和集群环境。
- Macvlan 网络:直接连接物理网络,适用于需要直接与物理网络通信的场景。
容器的数据如何持久化存储
在 Docker 中,容器的数据持久化存储是一个重要的概念,因为容器本身是短暂的,容器的文件系统在容器停止或删除后会丢失。为了确保数据在容器生命周期之外仍然保留,Docker 提供了几种持久化存储机制,包括数据卷(Volumes)、绑定挂载(Bind Mounts)和 tmpfs 挂载。
数据卷 Volume
是 Docker 提供的一种持久化存储机制,用于在容器之间共享数据或在容器重启、删除后保留数据。数据卷由 Docker 管理,存储在主机文件系统的特定位置。
- 独立于容器生命周期:数据卷的生命周期独立于容器,可以在容器删除后继续存在。
- 高效:数据卷的性能优于绑定挂载(Bind Mounts),因为它们由 Docker 管理和优化。
- 共享和重用:多个容器可以共享同一个数据卷,方便数据共享和重用。
- 备份和恢复:数据卷可以方便地进行备份和恢复。
- 支持多种存储驱动:数据卷可以使用不同的存储驱动,如本地存储、NFS、CIFS、云存储等。
自定义存储卷位置
使用 docker volume create
命令创建一个新的存储卷,并指定自定义位置。例如,将存储卷存储在 /custom/path/to/volume
目录下:
docker volume create --driver local --opt type=none --opt device=/custom/path/to/volume my-custom-volume
这将创建一个名为 my-custom-volume
的存储卷,并将其存储在 /custom/path/to/volume
目录下。请注意,你需要根据实际情况替换 /custom/path/to/volume
。
现在可以在创建容器时使用自定义存储卷,使用 docker run
命令创建容器,并使用 -v
或 --volume
参数将自定义存储卷挂载到容器中的指定位置。例如,将名为 my-custom-volume
的存储卷挂载到容器的 /app/data
目录:
docker run -d --name my-container -v my-custom-volume:/app/data my-image
在这个例子中,my-image
是你要运行的 Docker 镜像的名称,my-container
是新创建的容器名称。-v my-custom-volume:/app/data
参数表示将宿主机上的 my-custom-volume
存储卷挂载到容器的 /app/data
目录。
现在,当容器运行时,你可以在容器内的 /app/data
目录访问存储卷中的数据,也可以在宿主机上直接访问存储卷。这样,你就可以在容器和宿主机之间共享数据了。
绑定挂载(Bind Mounts)
绑定挂载将主机文件系统的特定目录挂载到容器内的目录。绑定挂载具有以下特点:
- 灵活性:可以挂载主机文件系统的任意目录。
- 实时同步:主机和容器之间的文件变化会实时同步。
运行容器并使用绑定挂载:
docker run -d --name my-container -v /path/on/host:/path/in/container nginx
tmpfs 挂载
tmpfs 挂载将数据存储在主机的内存中,而不是持久化存储。tmpfs 挂载适用于需要高性能临时存储的场景。
docker run -d --name my-container --tmpfs /path/in/container:rw,size=64m nginx
在这个示例中,容器内的 /path/in/container
目录使用 tmpfs 挂载,大小为 64MB。
总结
Docker 提供了多种持久化存储机制,包括数据卷、绑定挂载和 tmpfs 挂载。数据卷是 Docker 推荐的持久化存储方式,具有高效、独立于容器生命周期和方便共享的优点。绑定挂载提供了更大的灵活性,可以挂载主机文件系统的任意目录。tmpfs 挂载适用于需要高性能临时存储的场景。
容器的安全性
常见的安全威胁
- 不安全的镜像:使用不受信任或存在漏洞的镜像可能会引入安全风险。
- 容器逃逸:攻击者利用漏洞从容器逃逸到宿主机,获取更高的权限。
- 不安全的网络配置:不当的网络配置可能导致数据泄露或中间人攻击。
- 不安全的存储:未加密的存储卷可能导致数据泄露。
- 权限提升:容器内的进程获取了不必要的高权限,增加了攻击面。
- 不安全的配置:错误的配置可能导致容器暴露在外部网络中,增加被攻击的风险。
安全最佳实践
1. 使用可信的镜像
- 官方镜像:尽量使用 Docker 官方提供的镜像,这些镜像经过严格的安全审查。
- 镜像签名:使用 Docker Content Trust (DCT) 验证镜像的签名,确保镜像的来源可信。
export DOCKER_CONTENT_TRUST=1docker pull myimage:latest
- 镜像扫描:使用镜像扫描工具(如 Clair、Anchore、Trivy)扫描镜像中的漏洞。
trivy image myimage:latest
2. 最小化镜像
- 精简镜像:使用精简的基础镜像(如 Alpine),减少攻击面。
- 多阶段构建:使用多阶段构建减少最终镜像的大小和复杂性。
# 第一阶段:构建阶段FROM golang:1.16 AS builderWORKDIR /appCOPY . .RUN go build -o myapp
# 第二阶段:运行阶段FROM alpine:latestWORKDIR /appCOPY --from=builder /app/myapp .CMD ["./myapp"]
3. 限制容器权限
- 非特权用户:在容器内运行进程时,尽量使用非特权用户。
# DockerfileFROM nginx:latestUSER nginx
- 只读文件系统:将容器的文件系统设置为只读,防止未经授权的写入操作。
docker run --read-only myimage:latest
- 限制权限:使用
--cap-drop
和--cap-add
选项限制容器的权限。
docker run --cap-drop=ALL --cap-add=NET_ADMIN myimage:latest
4. 使用安全的网络配置
- 网络隔离:使用 Docker 网络模式(如 Bridge、Overlay)隔离容器网络。
- 防火墙规则:配置防火墙规则,限制容器之间和容器与外部网络之间的通信。
- 加密通信:使用 TLS 加密容器之间的通信,防止中间人攻击。
5. 安全的存储配置
- 加密存储:使用加密的存储卷,保护数据的机密性。
- 限制访问:限制容器对存储卷的访问权限,防止未经授权的访问。
docker run -v myvolume:/data:ro myimage:latest
6. 监控和日志
- 日志管理:集中管理和分析容器日志,及时发现和响应安全事件。
- 监控工具:使用监控工具(如 Prometheus、Grafana)监控容器的运行状态和性能。
7. 定期更新和补丁
- 更新镜像:定期更新基础镜像和应用镜像,确保使用最新的安全补丁。
- 自动化工具:使用自动化工具(如 CI/CD 管道)自动构建和部署更新的镜像。
其他
端口冲突问题
在 Docker 中,端口冲突是一个常见的问题,特别是在多个容器需要映射到主机的同一个端口时。由于主机端口是唯一的,不能同时将两个容器的端口映射到主机的同一个端口。如果尝试这样做,会导致端口冲突,Docker 会返回错误。
错误信息如下
docker: Error response from daemon: driver failed programming external connectivity on endpoint apache-container (container_id): Bind for 0.0.0.0:80 failed: port is already allocated.
解决方法
1. 使用不同的主机端口
2. 使用反向代理
使用一个反向代理(如 Nginx 或 Traefik)在主机上运行,并将流量路由到不同的容器。反向代理可以根据请求的 URL 或主机名将流量转发到相应的容器。
示例:使用 Nginx 作为反向代理
首先,运行两个容器,不映射到主机的 80 端口:
# 运行 Nginx 容器,映射到容器的 80 端口
docker run -d --name nginx-container nginx
# 运行 Apache 容器,映射到容器的 80 端口
docker run -d --name apache-container httpd
然后,运行一个 Nginx 反向代理容器,配置文件如下:
# nginx.conf
server {
listen 80;
location /nginx {
proxy_pass http://nginx-container:80;
}
location /apache {
proxy_pass http://apache-container:80;
}
}
将配置文件挂载到 Nginx 反向代理容器中:
docker run -d --name nginx-proxy -p 80:80 --link nginx-container --link apache-container -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf nginx
在这种情况下,可以通过 http://localhost/nginx
访问 Nginx 容器,通过 http://localhost/apache
访问 Apache 容器。
3. 使用 Docker Compose
在Docker Compose 中定义和运行多容器应用,并自动处理端口映射和网络配置。
version: '3'
services:
nginx:
image: nginx
ports:
- "80:80"
apache:
image: httpd
ports:
- "8080:80"
存储覆盖问题
当你将主机目录绑定挂载到容器中已经存在的目录时,会发生以下情况:
覆盖容器内的目录内容:绑定挂载会覆盖容器内的目标目录内容,显示主机目录的内容。容器内原有的目录内容将被隐藏,但不会被删除。
Mac 下创建自定义存储卷位置时报错
报错内容
docker: Error response from daemon: error while mounting volume ‘/var/lib/docker/volumes/my-custom-volume/_data’: failed to mount local volume: mount /Users/ryan/Documents/dockerfile/docker-data:/var/lib/docker/volumes/my-custom-volume/_data: no such device
首先得确保目录存在,其次要检查 Docker Desktop for Mac 已正确配置共享目录。你可以在 Docker Desktop 的设置中检查和配置共享目录。
- 打开 Docker Desktop。
- 进入 Preferences(偏好设置)。
- 选择 Resources > File Sharing。
- 确认
/Users/ryanjhzheng/Documents/dockerfile/docker-data
或其父目录已添加到共享目录列表中。如果没有,请添加它。
如果多个容器共用数据卷,万一在某个容器内将数据删除了怎么办
为了防止数据丢失和确保数据的安全性,可以采取以下几种策略:
- 定期备份数据卷
- 使用版本控制
- 使用只读数据卷
-v my-volume:/data:ro(ro 表示只读) - 使用容器编排工具,如使用 Kubernetes 持久卷(Persistent Volume)和持久卷声明(Persistent Volume Claim)